#include "sdk.h"
#include "projectmanagerui.h"
#include <algorithm>
#include <wx/checkbox.h>
#include <wx/choicdlg.h>
#include <wx/dir.h>
#include <wx/filedlg.h>
#include <wx/imaglist.h>
#include <wx/listctrl.h>
#include <wx/menu.h>
#include <wx/settings.h>
#include <wx/textdlg.h>
#include <wx/xrc/xmlres.h>
#include "configmanager.h"
#include "editormanager.h"
#include "logmanager.h"
#include <unordered_map>
#include "M_Code_auibook.h"
#include "M_Code_colourmanager.h"
#include "confirmreplacedlg.h"
#include "filefilters.h"
#include "filegroupsandmasks.h"
#include "multiselectdlg.h"
#include "projectdepsdlg.h"
#include "projectfileoptionsdlg.h"
#include "projectoptionsdlg.h"
#include "projectsfilemasksdlg.h"
#include "goto_file.h"

namespace {
  static const unsigned int MAX_OPEN_WITH_ITEMS = 20;
  static const int idOpenWith[] = {
    static_cast<int>( wxNewId() ), static_cast<int>( wxNewId() ), static_cast<int>( wxNewId() ), static_cast<int>( wxNewId() ), static_cast<int>( wxNewId() ),
    static_cast<int>( wxNewId() ), static_cast<int>( wxNewId() ), static_cast<int>( wxNewId() ), static_cast<int>( wxNewId() ), static_cast<int>( wxNewId() ),
    static_cast<int>( wxNewId() ), static_cast<int>( wxNewId() ), static_cast<int>( wxNewId() ), static_cast<int>( wxNewId() ), static_cast<int>( wxNewId() ),
    static_cast<int>( wxNewId() ), static_cast<int>( wxNewId() ), static_cast<int>( wxNewId() ), static_cast<int>( wxNewId() ), static_cast<int>( wxNewId() ),
  };
  static const int idOpenWithInternal = wxNewId();
  const int ID_ProjectManager = wxNewId();
  const int idMenuSetActiveProject = wxNewId();
  const int idMenuOpenFile = wxNewId();
  const int idMenuSaveProject = wxNewId();
  const int idMenuSaveFile = wxNewId();
  const int idMenuCloseProject = wxNewId();
  const int idMenuCloseFile = wxNewId();
  const int idMenuAddFilePopup = wxNewId();
  const int idMenuAddFilesRecursivelyPopup = wxNewId();
  const int idMenuAddFile = XRCID( "idMenuAddFile" );
  const int idMenuAddFilesRecursively = XRCID( "idMenuAddFilesRecursively" );
  const int idMenuRemoveFolderFilesPopup = wxNewId();
  const int idMenuOpenFolderFilesPopup = wxNewId();
  const int idMenuRemoveFilePopup = wxNewId();
  const int idMenuRemoveFile = XRCID( "idMenuRemoveFile" );
  const int idMenuRenameFile = wxNewId();
  const int idMenuRenameVFolder = wxNewId();
  const int idMenuProjectNotes = XRCID( "idMenuProjectNotes" );
  const int idMenuProjectProperties = XRCID( "idMenuProjectProperties" );
  const int idMenuFileProperties = wxNewId();
  const int idMenuTreeProjectProperties = wxNewId();
  const int idMenuTreeFileProperties = wxNewId();
  const int idMenuTreeOptionsCompile = wxNewId();
  const int idMenuTreeOptionsLink = wxNewId();
  const int idMenuTreeOptionsDisableBoth = wxNewId();
  const int idMenuTreeOptionsEnableBoth = wxNewId();
  const int idMenuGotoFile = XRCID( "idMenuGotoFile" );
  const int idMenuExecParams = XRCID( "idMenuExecParams" );
  const int idMenuViewCategorize = wxNewId();
  const int idMenuViewUseFolders = wxNewId();
  const int idMenuViewHideFolderName = wxNewId();
  const int idMenuViewFileMasks = wxNewId();
  const int idMenuNextProject = wxNewId();
  const int idMenuPriorProject = wxNewId();
  const int idMenuProjectTreeProps = wxNewId();
  const int idMenuProjectUp = wxNewId();
  const int idMenuProjectDown = wxNewId();
  const int idMenuViewCategorizePopup = wxNewId();
  const int idMenuViewUseFoldersPopup = wxNewId();
  const int idMenuViewHideFolderNamePopup  = wxNewId();
  const int idMenuTreeRenameWorkspace = wxNewId();
  const int idMenuTreeSaveWorkspace = wxNewId();
  const int idMenuTreeSaveAsWorkspace = wxNewId();
  const int idMenuTreeCloseWorkspace = wxNewId();
  const int idMenuAddVirtualFolder = wxNewId();
  const int idMenuDeleteVirtualFolder = wxNewId();
  const int idMenuFindFile = wxNewId();
  const int idNB = wxNewId();
  const int idNB_TabTop = wxNewId();
  const int idNB_TabBottom = wxNewId();
}

namespace {
  bool ProjectCanDragNode( M_Code_Project* project, wxTreeCtrl* tree, wxTreeItemId node );
  bool ProjectNodeDragged( M_Code_Project* project, wxTreeCtrl* tree, wxArrayTreeItemIds& fromArray, wxTreeItemId to );
  bool ProjectVirtualFolderAdded( M_Code_Project* project, wxTreeCtrl* tree, wxTreeItemId parent_node, const wxString& virtual_folder );
  void ProjectVirtualFolderDeleted( M_Code_Project* project, wxTreeCtrl* tree, wxTreeItemId node );
  bool ProjectVirtualFolderRenamed( M_Code_Project* project, wxTreeCtrl* tree, wxTreeItemId node, const wxString& new_name );
  bool ProjectVirtualFolderDragged( M_Code_Project* project, wxTreeCtrl* tree, wxTreeItemId from, wxTreeItemId to );
  bool ProjectShowOptions( M_Code_Project* project );
}

BEGIN_EVENT_TABLE( ProjectManagerUI, wxEvtHandler )
  EVT_TREE_BEGIN_DRAG( ID_ProjectManager, ProjectManagerUI::OnTreeBeginDrag )
  EVT_TREE_END_DRAG( ID_ProjectManager, ProjectManagerUI::OnTreeEndDrag )
  EVT_TREE_BEGIN_LABEL_EDIT( ID_ProjectManager, ProjectManagerUI::OnBeginEditNode )
  EVT_TREE_END_LABEL_EDIT( ID_ProjectManager, ProjectManagerUI::OnEndEditNode )
  EVT_TREE_ITEM_ACTIVATED( ID_ProjectManager, ProjectManagerUI::OnProjectFileActivated )
  EVT_TREE_ITEM_RIGHT_CLICK( ID_ProjectManager, ProjectManagerUI::OnTreeItemRightClick )
  EVT_TREE_KEY_DOWN( ID_ProjectManager, ProjectManagerUI::OnKeyDown )
  EVT_COMMAND_RIGHT_CLICK( ID_ProjectManager, ProjectManagerUI::OnRightClick )
  EVT_AUINOTEBOOK_TAB_RIGHT_UP( idNB, ProjectManagerUI::OnTabContextMenu )
  EVT_MENU_RANGE( idOpenWith[0], idOpenWith[MAX_OPEN_WITH_ITEMS - 1], ProjectManagerUI::OnOpenWith )
  EVT_MENU( idOpenWithInternal, ProjectManagerUI::OnOpenWith )
  EVT_MENU( idNB_TabTop, ProjectManagerUI::OnTabPosition )
  EVT_MENU( idNB_TabBottom, ProjectManagerUI::OnTabPosition )
  EVT_MENU( idMenuSetActiveProject, ProjectManagerUI::OnSetActiveProject )
  EVT_MENU( idMenuNextProject, ProjectManagerUI::OnSetActiveProject )
  EVT_MENU( idMenuPriorProject, ProjectManagerUI::OnSetActiveProject )
  EVT_MENU( idMenuProjectUp, ProjectManagerUI::OnSetActiveProject )
  EVT_MENU( idMenuProjectDown, ProjectManagerUI::OnSetActiveProject )
  EVT_MENU( idMenuTreeRenameWorkspace, ProjectManagerUI::OnRenameWorkspace )
  EVT_MENU( idMenuTreeSaveWorkspace, ProjectManagerUI::OnSaveWorkspace )
  EVT_MENU( idMenuTreeSaveAsWorkspace, ProjectManagerUI::OnSaveAsWorkspace )
  EVT_MENU( idMenuTreeCloseWorkspace, ProjectManagerUI::OnCloseWorkspace )
  EVT_MENU( idMenuAddVirtualFolder, ProjectManagerUI::OnAddVirtualFolder )
  EVT_MENU( idMenuDeleteVirtualFolder, ProjectManagerUI::OnDeleteVirtualFolder )
  EVT_MENU( idMenuAddFile, ProjectManagerUI::OnAddFileToProject )
  EVT_MENU( idMenuAddFilesRecursively, ProjectManagerUI::OnAddFilesToProjectRecursively )
  EVT_MENU( idMenuRemoveFile, ProjectManagerUI::OnRemoveFileFromProject )
  EVT_MENU( idMenuAddFilePopup, ProjectManagerUI::OnAddFileToProject )
  EVT_MENU( idMenuAddFilesRecursivelyPopup, ProjectManagerUI::OnAddFilesToProjectRecursively )
  EVT_MENU( idMenuRemoveFolderFilesPopup, ProjectManagerUI::OnRemoveFileFromProject )
  EVT_MENU( idMenuOpenFolderFilesPopup, ProjectManagerUI::OnOpenFolderFiles )
  EVT_MENU( idMenuRemoveFilePopup, ProjectManagerUI::OnRemoveFileFromProject )
  EVT_MENU( idMenuRenameFile, ProjectManagerUI::OnRenameFile )
  EVT_MENU( idMenuRenameVFolder, ProjectManagerUI::OnRenameVirtualFolder )
  EVT_MENU( idMenuSaveProject, ProjectManagerUI::OnSaveProject )
  EVT_MENU( idMenuSaveFile, ProjectManagerUI::OnSaveFile )
  EVT_MENU( idMenuCloseProject, ProjectManagerUI::OnCloseProject )
  EVT_MENU( idMenuCloseFile, ProjectManagerUI::OnCloseFile )
  EVT_MENU( idMenuOpenFile, ProjectManagerUI::OnOpenFile )
  EVT_MENU( idMenuProjectNotes, ProjectManagerUI::OnNotes )
  EVT_MENU( idMenuProjectProperties, ProjectManagerUI::OnProperties )
  EVT_MENU( idMenuFileProperties, ProjectManagerUI::OnProperties )
  EVT_MENU( idMenuTreeOptionsCompile, ProjectManagerUI::OnFileOptions )
  EVT_MENU( idMenuTreeOptionsLink, ProjectManagerUI::OnFileOptions )
  EVT_MENU( idMenuTreeOptionsEnableBoth, ProjectManagerUI::OnFileOptions )
  EVT_MENU( idMenuTreeOptionsDisableBoth, ProjectManagerUI::OnFileOptions )
  EVT_MENU( idMenuTreeProjectProperties, ProjectManagerUI::OnProperties )
  EVT_MENU( idMenuTreeFileProperties, ProjectManagerUI::OnProperties )
  EVT_MENU( idMenuGotoFile, ProjectManagerUI::OnGotoFile )
  EVT_MENU( idMenuExecParams, ProjectManagerUI::OnExecParameters )
  EVT_MENU( idMenuViewCategorize, ProjectManagerUI::OnViewCategorize )
  EVT_MENU( idMenuViewUseFolders, ProjectManagerUI::OnViewUseFolders )
  EVT_MENU( idMenuViewHideFolderName, ProjectManagerUI::OnViewHideFolderName )
  EVT_MENU( idMenuViewCategorizePopup, ProjectManagerUI::OnViewCategorize )
  EVT_MENU( idMenuViewUseFoldersPopup, ProjectManagerUI::OnViewUseFolders )
  EVT_MENU( idMenuViewHideFolderNamePopup,  ProjectManagerUI::OnViewHideFolderName )
  EVT_MENU( idMenuViewFileMasks, ProjectManagerUI::OnViewFileMasks )
  EVT_MENU( idMenuFindFile, ProjectManagerUI::OnFindFile )
  EVT_IDLE( ProjectManagerUI::OnIdle )
  EVT_UPDATE_UI( idMenuFileProperties, ProjectManagerUI::OnUpdateUI )
  EVT_UPDATE_UI( idMenuProjectProperties, ProjectManagerUI::OnUpdateUI )
  EVT_UPDATE_UI( idMenuAddFile, ProjectManagerUI::OnUpdateUI )
  EVT_UPDATE_UI( idMenuAddFilesRecursively, ProjectManagerUI::OnUpdateUI )
  EVT_UPDATE_UI( idMenuRemoveFile, ProjectManagerUI::OnUpdateUI )
  EVT_UPDATE_UI( idMenuProjectTreeProps, ProjectManagerUI::OnUpdateUI )
  EVT_UPDATE_UI( idMenuAddVirtualFolder, ProjectManagerUI::OnUpdateUI )
  EVT_UPDATE_UI( idMenuDeleteVirtualFolder, ProjectManagerUI::OnUpdateUI )
END_EVENT_TABLE()

ProjectManagerUI::ProjectManagerUI() :
  m_pTree( nullptr ),
  m_TreeFreezeCounter( 0 ),
  m_isCheckingForExternallyModifiedProjects( false ) {
  m_pNotebook = new M_Code_AuiNotebook( Manager::Get()->GetAppWindow(), idNB,
                                        wxDefaultPosition, wxDefaultSize, wxAUI_NB_WINDOWLIST_BUTTON );
  if( Manager::Get()->GetConfigManager( _T( "app" ) )->ReadBool( _T( "/environment/project_tabs_bottom" ), false ) ) {
    m_pNotebook->SetWindowStyleFlag( m_pNotebook->GetWindowStyleFlag() | wxAUI_NB_BOTTOM );
  }
  InitPane();
  ConfigManager *cfg = Manager::Get()->GetConfigManager( _T( "project_manager" ) );
  m_TreeVisualState  = ptvsNone;
  m_TreeVisualState |= ( cfg->ReadBool( _T( "/categorize_tree" ),  true )  ? ptvsCategorize     : ptvsNone );
  m_TreeVisualState |= ( cfg->ReadBool( _T( "/use_folders" ),      true )  ? ptvsUseFolders     : ptvsNone );
  m_TreeVisualState |= ( cfg->ReadBool( _T( "/hide_folder_name" ), false ) ? ptvsHideFolderName : ptvsNone );
  if( ( m_TreeVisualState & ptvsUseFolders ) && ( m_TreeVisualState & ptvsHideFolderName ) ) {
    m_TreeVisualState &= ~ptvsHideFolderName;
    cfg->Write( _T( "/hide_folder_name" ), false );
  }
  RebuildTree();
  Manager::Get()->GetColourManager()->RegisterColour( _T( "项目树" ), _T( "未编译的文件（头/资源）" ),
      _T( "project_tree_non_source_files" ),
      wxSystemSettings::GetColour( wxSYS_COLOUR_GRAYTEXT ) );
  Manager::Get()->GetAppWindow()->PushEventHandler( this );
}

ProjectManagerUI::~ProjectManagerUI() {
  delete m_pImages;
  m_pImages = nullptr;
  m_pNotebook->Destroy();
}

void ProjectManagerUI::InitPane() {
  if( Manager::IsAppShuttingDown() ) {
    return;
  }
  if( m_pTree ) {
    return;
  }
  BuildTree();
  m_pNotebook->AddPage( m_pTree, _T( "项目" ) );
}

void ProjectManagerUI::BuildTree() {
  m_pTree = new M_Code_TreeCtrl( m_pNotebook, ID_ProjectManager );
  static const wxString imgs[] = {
    _T( "file.png" ),
    _T( "file-missing.png" ),
    _T( "file-modified.png" ),
    _T( "file-readonly.png" ),
    _T( "rc-file-added.png" ),
    _T( "rc-file-conflict.png" ),
    _T( "rc-file-missing.png" ),
    _T( "rc-file-modified.png" ),
    _T( "rc-file-outofdate.png" ),
    _T( "rc-file-uptodate.png" ),
    _T( "rc-file-requireslock.png" ),
    _T( "rc-file-external.png" ),
    _T( "rc-file-gotlock.png" ),
    _T( "rc-file-lockstolen.png" ),
    _T( "rc-file-mismatch.png" ),
    _T( "rc-file-noncontrolled.png" ),
    _T( "workspace.png" ),
    _T( "workspace-readonly.png" ),
    _T( "project.png" ),
    _T( "project-readonly.png" ),
    _T( "folder_open.png" ),
    _T( "vfolder_open.png" ),
    wxEmptyString
  };
  wxBitmap bmp;
  m_pImages = new wxImageList( 16, 16 );
  wxString prefix = ConfigManager::ReadDataPath() + _T( "/images/" );
  int i = 0;
  while( !imgs[i].IsEmpty() ) {
    bmp = M_Code_LoadBitmap( prefix + imgs[i], wxBITMAP_TYPE_PNG );
    m_pImages->Add( bmp );
    ++i;
  }
  m_pTree->SetImageList( m_pImages );
}

void ProjectManagerUI::RebuildTree() {
  if( Manager::IsAppShuttingDown() ) {
    return;
  }
  FreezeTree();
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  ProjectsArray* pa = pm->GetProjects();
  int count = pa->GetCount();
  for( int i = 0; i < count; ++i ) {
    if( M_Code_Project* prj = pa->Item( i ) ) {
      prj->SaveTreeState( m_pTree );
    }
  }
  m_pTree->DeleteAllItems();
  wxString title;
  bool read_only = false;
  M_Code_Workspace* wkspc = pm->GetWorkspace();
  if( wkspc ) {
    title = wkspc->GetTitle();
    wxString ws_file = wkspc->GetFilename();
    read_only = ( !ws_file.IsEmpty() &&  wxFile::Exists( ws_file.c_str() )
                  && !wxFile::Access( ws_file.c_str(), wxFile::write ) );
  }
  if( title.IsEmpty() ) {
    title = _T( "工作空间" );
  }
  m_TreeRoot = m_pTree->AddRoot( title, cbProjectTreeImages::WorkspaceIconIndex( read_only ), cbProjectTreeImages::WorkspaceIconIndex( read_only ) );
  for( int i = 0; i < count; ++i ) {
    if( M_Code_Project* prj = pa->Item( i ) ) {
      BuildProjectTree( prj, m_pTree, m_TreeRoot, m_TreeVisualState, pm->GetFilesGroupsAndMasks() );
      m_pTree->SetItemBold( prj->GetProjectNode(), prj == pm->GetActiveProject() );
    }
  }
  m_pTree->Expand( m_TreeRoot );
  for( int i = 0; i < count; ++i ) {
    if( M_Code_Project* prj = pa->Item( i ) ) {
      prj->RestoreTreeState( m_pTree );
    }
  }
  UnfreezeTree();
}

void ProjectManagerUI::FreezeTree() {
  if( !m_pTree ) {
    return;
  }
  ++m_TreeFreezeCounter;
  m_pTree->Freeze();
}

void ProjectManagerUI::UnfreezeTree( M_Code_unused bool force ) {
  if( !m_pTree ) {
    return;
  }
  if( m_TreeFreezeCounter ) {
    --m_TreeFreezeCounter;
    m_pTree->Thaw();
  }
}

void ProjectManagerUI::UpdateActiveProject( M_Code_Project* oldProject, M_Code_Project* newProject, bool refresh ) {
  if( oldProject ) {
    m_pTree->SetItemBold( oldProject->GetProjectNode(), false );
  }
  if( newProject ) {
    wxTreeItemId tid = newProject->GetProjectNode();
    if( tid ) {
      m_pTree->SetItemBold( newProject->GetProjectNode(), true );
    }
  }
  if( refresh ) {
    RebuildTree();
  }
  if( newProject ) {
    m_pTree->EnsureVisible( newProject->GetProjectNode() );
  }
  m_pTree->Refresh();
}

void ProjectManagerUI::RemoveProject( M_Code_Project* project ) {
  m_pTree->Delete( project->GetProjectNode() );
}

wxTreeItemId ProjectManagerUI::GetTreeSelection() {
  if( m_RightClickItem.IsOk() ) {
    return m_RightClickItem;
  }
  wxArrayTreeItemIds selections;
  unsigned int sel = m_pTree->GetSelections( selections );
  if( sel ) {
    return selections[0];
  }
  return wxTreeItemId();
}

void ProjectManagerUI::BeginLoadingWorkspace() {
  FreezeTree();
  m_pTree->AppendItem( m_pTree->GetRootItem(), _T( "正在加载工作区..." ) );
  m_pTree->Expand( m_pTree->GetRootItem() );
  UnfreezeTree();
}

void ProjectManagerUI::CloseWorkspace() {
  if( m_pTree ) {
    m_pTree->SetItemText( m_TreeRoot, _T( "Workspace" ) );
    if( !Manager::IsAppShuttingDown() ) {
      RebuildTree();
    }
  }
}

void ProjectManagerUI::FinishLoadingProject( M_Code_Project* project, bool newAddition, M_Code_unused FilesGroupsAndMasks* fgam ) {
  if( newAddition ) {
    ProjectManager* pm = Manager::Get()->GetProjectManager();
    BuildProjectTree( project, m_pTree, m_TreeRoot, m_TreeVisualState, pm->GetFilesGroupsAndMasks() );
  } else {
    RebuildTree();
  }
  m_pTree->Expand( project->GetProjectNode() );
  m_pTree->Expand( m_TreeRoot );
}

void ProjectManagerUI::FinishLoadingWorkspace( M_Code_Project* activeProject, const wxString &workspaceTitle ) {
  RebuildTree();
  if( activeProject ) {
    m_pTree->Expand( activeProject->GetProjectNode() );
  }
  m_pTree->Expand( m_TreeRoot );
  m_pTree->SetItemText( m_TreeRoot, workspaceTitle );
  UnfreezeTree( true );
}

void ProjectManagerUI::SwitchToProjectsPage() {
  CodeBlocksDockEvent showEvent( cbEVT_SHOW_DOCK_WINDOW );
  showEvent.pWindow = m_pNotebook;
  Manager::Get()->ProcessEvent( showEvent );
  int page = m_pNotebook->GetPageIndex( m_pTree );
  if( page != wxNOT_FOUND ) {
    m_pNotebook->SetSelection( page );
  }
}

void ProjectManagerUI::ShowFileInTree( ProjectFile &projectFile ) {
  m_pTree->UnselectAll();
  const wxTreeItemId &itemId = projectFile.GetTreeItemId();
  if( itemId.IsOk() ) {
    m_pTree->EnsureVisible( itemId );
    m_pTree->SelectItem( itemId, true );
  }
}

void ProjectManagerUI::CreateMenuTreeProps( wxMenu* menu, bool popup ) {
  wxMenu* treeprops = new wxMenu;
  treeprops->Append( idMenuProjectUp, _T( "上移项目\tCtrl-Shift-Up" ) );
  treeprops->Append( idMenuProjectDown, _T( "下移项目\tCtrl-Shift-Down" ) );
  treeprops->Append( idMenuPriorProject, _T( "激活上一个项目\tAlt-F5" ) );
  treeprops->Append( idMenuNextProject,  _T( "激活下一个项目\tAlt-F6" ) );
  treeprops->AppendCheckItem( ( popup ? idMenuViewCategorizePopup : idMenuViewCategorize ),
                              _T( "按文件类型分类" ) );
  treeprops->AppendCheckItem( ( popup ? idMenuViewUseFoldersPopup : idMenuViewUseFolders ),
                              _T( "在磁盘上显示文件夹" ) );
  treeprops->AppendCheckItem( ( popup ? idMenuViewHideFolderNamePopup : idMenuViewHideFolderName ),
                              _T( "隐藏文件夹名称" ) );
  ConfigManager *cfg = Manager::Get()->GetConfigManager( _T( "project_manager" ) );
  bool do_categorise = cfg->ReadBool( _T( "/categorize_tree" ), true );
  bool do_use_folders = cfg->ReadBool( _T( "/use_folders" ), true );
  bool do_hide_folder_name = !do_use_folders && cfg->ReadBool( _T( "/hide_folder_name" ), false );
  cfg->Write( _T( "/hide_folder_name" ), do_hide_folder_name );
  treeprops->Check( ( popup ? idMenuViewCategorizePopup : idMenuViewCategorize ), do_categorise );
  treeprops->Check( ( popup ? idMenuViewUseFoldersPopup : idMenuViewUseFolders ), do_use_folders );
  treeprops->Check( ( popup ? idMenuViewHideFolderNamePopup : idMenuViewHideFolderName ), do_hide_folder_name );
  treeprops->Enable( ( popup ? idMenuViewUseFoldersPopup : idMenuViewUseFolders ), !do_hide_folder_name );
  treeprops->Enable( ( popup ? idMenuViewHideFolderNamePopup : idMenuViewHideFolderName ), !do_use_folders );
  treeprops->Append( idMenuViewFileMasks, _T( "编辑文件类型和类别..." ) );
  menu->Append( idMenuProjectTreeProps, _T( "项目树" ), treeprops );
}

void ProjectManagerUI::ShowMenu( wxTreeItemId id, const wxPoint& pt ) {
  if( !id.IsOk() ) {
    return;
  }
  wxString caption;
  wxMenu menu;
  FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( id );
  bool is_vfolder = ftd && ftd->GetKind() == FileTreeData::ftdkVirtualFolder;
  bool PopUpMenuOption = true;
  ProjectsArray* pa = Manager::Get()->GetProjectManager()->GetProjects();
  if( pa && ftd && ( ftd->GetKind() == FileTreeData::ftdkProject
                     || ftd->GetKind() == FileTreeData::ftdkFile
                     || ftd->GetKind() == FileTreeData::ftdkFolder ) ) {
    PopUpMenuOption = !cbHasRunningCompilers( Manager::Get()->GetPluginManager() );
  }
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  if( ftd ) {
    if( ftd->GetKind() == FileTreeData::ftdkProject ) {
      if( ftd->GetProject() != pm->GetActiveProject() ) {
        menu.Append( idMenuSetActiveProject, _T( "激活项目" ) );
        menu.Enable( idMenuSetActiveProject, PopUpMenuOption );
      }
      menu.Append( idMenuSaveProject, _T( "保存项目" ) );
      menu.Enable( idMenuSaveProject, PopUpMenuOption );
      menu.Append( idMenuCloseProject, _T( "关闭项目" ) );
      menu.Enable( idMenuCloseProject, PopUpMenuOption );
      menu.Append( idMenuAddFilePopup, _T( "添加文件..." ) );
      menu.Enable( idMenuAddFilePopup, PopUpMenuOption );
      menu.Append( idMenuAddFilesRecursivelyPopup, _T( "递归添加文件..." ) );
      menu.Enable( idMenuAddFilesRecursivelyPopup, PopUpMenuOption );
      menu.Append( idMenuRemoveFile, _T( "删除文件..." ) );
      menu.Append( idMenuFindFile, _T( "查找文件..." ) );
      CreateMenuTreeProps( &menu, true );
      menu.Append( idMenuAddVirtualFolder, _T( "添加新虚拟文件夹" ) );
      if( is_vfolder ) {
        menu.Append( idMenuDeleteVirtualFolder, _T( "删除此虚拟文件夹" ) );
      }
    } else if( ftd->GetKind() == FileTreeData::ftdkFile ) {
      ProjectFile* pf = ftd->GetProjectFile();
      EditorBase* eb = Manager::Get()->GetEditorManager()->IsOpen( pf->file.GetFullPath() );
      if( eb ) { // 项目书右键
        bool active = Manager::Get()->GetEditorManager()->GetActiveEditor() == eb;
        if( !active ) {
          caption.Printf( _T( "切换到 %s" ), m_pTree->GetItemText( id ).c_str() );
          menu.Append( idMenuOpenFile, caption );
        }
        caption.Printf( _T( "保存 %s" ), m_pTree->GetItemText( id ).c_str() );
        menu.Append( idMenuSaveFile, caption );
        caption.Printf( _T( "关闭 %s" ), m_pTree->GetItemText( id ).c_str() );
        menu.Append( idMenuCloseFile, caption );
      } else {
        caption.Printf( _T( "打开 %s" ), m_pTree->GetItemText( id ).c_str() );
        menu.Append( idMenuOpenFile, caption );
      }
      wxMenu* openWith = new wxMenu;
      PluginsArray mimes = Manager::Get()->GetPluginManager()->GetMimeOffers();
      for( unsigned int i = 0; i < mimes.GetCount() && i < MAX_OPEN_WITH_ITEMS; ++i ) {
        cbMimePlugin* plugin = ( cbMimePlugin* )mimes[i];
        if( plugin && plugin->CanHandleFile( m_pTree->GetItemText( id ) ) ) {
          const PluginInfo* info = Manager::Get()->GetPluginManager()->GetPluginInfo( plugin );
          openWith->Append( idOpenWith[i], info ? info->title : wxString( _T( "<Unknown plugin>" ) ) );
        }
      }
      openWith->Append( idOpenWithInternal, _T( "内部编辑器" ) );
      menu.Append( wxID_ANY, _T( "打开" ), openWith );
      if( pf->GetFileState() == fvsNormal && !Manager::Get()->GetEditorManager()->IsOpen( pf->file.GetFullPath() ) ) {
        menu.Append( idMenuRenameFile, _T( "重命名文件" ) );
        menu.Enable( idMenuRenameFile, PopUpMenuOption );
      }
      menu.Append( idMenuRemoveFilePopup, _T( "从项目中移除文件" ) );
      menu.Enable( idMenuRemoveFilePopup, PopUpMenuOption );
    } else if( ftd->GetKind() == FileTreeData::ftdkFolder ) {
      menu.Append( idMenuAddFilePopup, _T( "添加文件" ) );
      menu.Enable( idMenuAddFilePopup, PopUpMenuOption );
      menu.Append( idMenuAddFilesRecursivelyPopup, _T( "递归添加文件" ) );
      menu.Enable( idMenuAddFilesRecursivelyPopup, PopUpMenuOption );
      menu.Append( idMenuRemoveFile, _T( "删除文件" ) );
      menu.Append( idMenuFindFile, _T( "查找文件" ) );
      wxFileName f( ftd->GetFolder() );
      f.MakeRelativeTo( ftd->GetProject()->GetCommonTopLevelPath() );
      menu.Append( idMenuRemoveFolderFilesPopup, F( _T( "删除 %s*" ), f.GetFullPath().c_str() ) );
      menu.Enable( idMenuRemoveFolderFilesPopup, PopUpMenuOption );
      menu.Append( idMenuOpenFolderFilesPopup, F( _T( "打开 %s*" ), f.GetFullPath().c_str() ) );
    } else if( is_vfolder ) {
      menu.Append( idMenuAddVirtualFolder, _T( "添加新虚拟文件夹..." ) );
      menu.Append( idMenuDeleteVirtualFolder, _T( "删除此虚拟文件夹" ) );
      menu.Append( idMenuRenameVFolder, _T( "重命名此虚拟文件夹" ) );
      menu.Append( idMenuRemoveFile, _T( "删除文件" ) );
      menu.Append( idMenuRemoveFolderFilesPopup, F( _T( "删除 %s*" ), ftd->GetFolder().c_str() ) );
      menu.Append( idMenuOpenFolderFilesPopup, F( _T( "打开 %s*" ), ftd->GetFolder().c_str() ) );
      menu.Append( idMenuFindFile, _T( "查找文件" ) );
    } else if( ftd->GetKind() == FileTreeData::ftdkVirtualGroup ) {
      menu.Append( idMenuFindFile, _T( "查找文件" ) );
    }
    Manager::Get()->GetPluginManager()->AskPluginsForModuleMenu( mtProjectManager, &menu, ftd );
    if( ftd->GetKind() == FileTreeData::ftdkProject ) { // 项目树右键
      menu.Append( idMenuTreeProjectProperties, _T( "属性" ) );
      menu.Enable( idMenuTreeProjectProperties, PopUpMenuOption );
    } else if( ftd->GetKind() == FileTreeData::ftdkFile ) { // 项目树文件右键
      wxMenu *options = new wxMenu;
      wxMenuItem *optionsItem = menu.AppendSubMenu( options, _T( "选项" ) );
      optionsItem->Enable( PopUpMenuOption );
      options->AppendCheckItem( idMenuTreeOptionsCompile, _T( "编译文件" ) );
      options->AppendCheckItem( idMenuTreeOptionsLink, _T( "链接文件" ) );
      options->Append( idMenuTreeOptionsEnableBoth, _T( "启用both" ) );
      options->Append( idMenuTreeOptionsDisableBoth, _T( "禁用both" ) );
      if( ProjectFile* pf = ftd->GetProjectFile() ) {
        menu.Check( idMenuTreeOptionsCompile, pf->compile );
        menu.Check( idMenuTreeOptionsLink, pf->link );
      }
      if( wxGetKeyState( WXK_CONTROL ) ) { //转换属性_菜单
        menu.Append( idMenuTreeProjectProperties, _T( "属性" ) );
      } else {
        menu.Append( idMenuTreeFileProperties, _T( "属性" ) );
      }
      menu.Enable( idMenuTreeFileProperties, PopUpMenuOption );
    }
  } else if( !ftd && pm->GetWorkspace() ) {
    wxCommandEvent event;
    OnRightClick( event );
    return;
  }
  if( menu.GetMenuItemCount() != 0 ) {
    m_pTree->PopupMenu( &menu, pt );
  }
}

void ProjectManagerUI::DoOpenFile( ProjectFile* pf, const wxString& filename ) {
  wxFileName the_file( filename );
  if( !the_file.FileExists() ) {
    wxString msg;
    msg.Printf( _T( "无法打开文件 '%s'.\n文件不存在." ), filename.c_str() );
    msging( msg, _T( "错误" ) );
    pf->SetFileState( fvsMissing );
    Manager::Get()->GetLogManager()->LogError( msg );
    return;
  }
  FileType ft = FileTypeOf( filename );
  if( ft == ftHeader || ft == ftSource || ft == ftTemplateSource ) {
    if( M_Code_Editor* ed = Manager::Get()->GetEditorManager()->Open( filename ) ) {
      ed->SetProjectFile( pf );
      ed->Activate();
    } else {
      wxString msg;
      msg.Printf( _T( "打开失败 '%s'." ), filename.c_str() );
      Manager::Get()->GetLogManager()->LogError( msg );
    }
  } else {
    EditorBase* eb = Manager::Get()->GetEditorManager()->IsOpen( filename );
    if( eb && !eb->IsBuiltinEditor() ) {
      eb->Activate();
      return;
    }
    cbMimePlugin* plugin = Manager::Get()->GetPluginManager()->GetMIMEHandlerForFile( filename );
    if( !plugin ) {
      wxString msg;
      msg.Printf( _T( "无法打开文件 '%s'.\n没有为此类型的文件注册处理程序." ), filename.c_str() );
      Manager::Get()->GetLogManager()->LogError( msg );
    } else if( plugin->OpenFile( filename ) != 0 ) {
      const PluginInfo* info = Manager::Get()->GetPluginManager()->GetPluginInfo( plugin );
      wxString msg;
      msg.Printf( _T( "无法打开文件 '%s'.\n注册的处理程序 (%s) 无法打开它." ), filename.c_str(), info ? info->title.c_str() : wxString( _T( "<Unknown plugin>" ) ).c_str() );
      Manager::Get()->GetLogManager()->LogError( msg );
    }
  }
}

void ProjectManagerUI::DoOpenSelectedFile() {
  wxTreeItemId sel = GetTreeSelection();
  if( !sel.IsOk() ) {
    return;
  }
  if( FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( sel ) ) {
    if( ProjectFile* pf = ftd->GetProjectFile() ) {
      DoOpenFile( pf, pf->file.GetFullPath() );
    }
  }
}

void ProjectManagerUI::RemoveFilesRecursively( wxTreeItemId& sel_id ) {
  wxTreeItemIdValue cookie;
  wxTreeItemId child;
  wxString filename;
  size_t i = 0;
  while( i < m_pTree->GetChildrenCount( sel_id ) ) {
    if( i == 0 ) {
      child = m_pTree->GetFirstChild( sel_id, cookie );
    } else
    { child = m_pTree->GetNextChild( sel_id, cookie ); }
    if( child.IsOk() ) {
      if( FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( child ) ) {
        M_Code_Project* prj = ftd->GetProject();
        if( prj && ftd->GetKind() == FileTreeData::ftdkFile ) {
          if( ProjectFile* pf = ftd->GetProjectFile() ) {
            Manager::Get()->GetProjectManager()->RemoveFileFromProject( pf, prj );
          }
        } else if( ftd->GetKind() == FileTreeData::ftdkFolder || ftd->GetKind() == FileTreeData::ftdkVirtualFolder ) {
          RemoveFilesRecursively( child );
        }
      }
      ++i;
    } else
    { break; }
  }
}

void ProjectManagerUI::OpenFilesRecursively( wxTreeItemId& sel_id ) {
  wxTreeItemIdValue cookie;
  wxTreeItemId child;
  wxString filename;
  size_t i = 0;
  while( i < m_pTree->GetChildrenCount( sel_id ) ) {
    if( i == 0 ) {
      child = m_pTree->GetFirstChild( sel_id, cookie );
    } else
    { child = m_pTree->GetNextChild( sel_id, cookie ); }
    if( child.IsOk() ) {
      FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( child );
      if( ftd ) {
        M_Code_Project* prj = ftd->GetProject();
        if( prj && ftd->GetKind() == FileTreeData::ftdkFile ) {
          if( ProjectFile* pf = ftd->GetProjectFile() ) {
            DoOpenFile( pf, pf->file.GetFullPath() );
          }
        } else if( ftd->GetKind() == FileTreeData::ftdkFolder || ftd->GetKind() == FileTreeData::ftdkVirtualFolder ) {
          OpenFilesRecursively( child );
        }
      }
      ++i;
    } else
    { break; }
  }
}



void ProjectManagerUI::OnTabContextMenu( M_Code_unused wxAuiNotebookEvent& event ) {
  wxMenu* NBmenu = new wxMenu();
  if( Manager::Get()->GetConfigManager( _T( "app" ) )->ReadBool( _T( "/environment/project_tabs_bottom" ), false ) ) {
    NBmenu->Append( idNB_TabTop, _T( "顶部标签" ) );
  } else {
    NBmenu->Append( idNB_TabBottom, _T( "底部标签" ) );
  }
  m_pNotebook->PopupMenu( NBmenu );
  delete NBmenu;
}

void ProjectManagerUI::OnTabPosition( wxCommandEvent& event ) {
  long style = m_pNotebook->GetWindowStyleFlag();
  style &= ~wxAUI_NB_BOTTOM;
  if( event.GetId() == idNB_TabBottom ) {
    style |= wxAUI_NB_BOTTOM;
  }
  m_pNotebook->SetWindowStyleFlag( style );
  m_pNotebook->Refresh();
  Manager::Get()->GetConfigManager( _T( "app" ) )->Write( _T( "/environment/project_tabs_bottom" ), ( bool )( style & wxAUI_NB_BOTTOM ) );
}

void ProjectManagerUI::OnTreeBeginDrag( wxTreeEvent& event ) {
  size_t count = m_pTree->GetSelections( m_DraggingSelection );
  for( size_t i = 0; i < count; i++ ) {
    wxTreeItemId id = m_DraggingSelection[i];
    if( !id.IsOk() ) {
      return;
    }
    FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( id );
    if( !ftd ) {
      return;
    }
    M_Code_Project* prj = ftd->GetProject();
    if( !prj ) {
      return;
    }
    if( !ProjectCanDragNode( prj, m_pTree, id ) ) {
      return;
    }
  }
  event.Allow();
}

void ProjectManagerUI::OnTreeEndDrag( wxTreeEvent& event ) {
  wxTreeItemId to = event.GetItem();
  if( !to.IsOk() ) {
    return;
  }
  FileTreeData* ftdTo = ( FileTreeData* )m_pTree->GetItemData( to );
  if( !ftdTo ) {
    return;
  }
  M_Code_Project* prjTo = ftdTo->GetProject();
  if( !prjTo ) {
    return;
  }
  size_t count = m_DraggingSelection.Count();
  for( size_t i = 0; i < count; i++ ) {
    wxTreeItemId from = m_DraggingSelection[i];
    if( !from.IsOk() ) {
      return;
    }
    FileTreeData* ftdFrom = ( FileTreeData* )m_pTree->GetItemData( from );
    if( !ftdFrom ) {
      return;
    }
    M_Code_Project* prjFrom = ftdTo->GetProject();
    if( prjFrom != prjTo ) {
      return;
    }
  }
  if( !ProjectNodeDragged( prjTo, m_pTree, m_DraggingSelection, to ) ) {
    return;
  }
  event.Allow();
}

void ProjectManagerUI::OnProjectFileActivated( wxTreeEvent& event ) {
  wxTreeItemId id = event.GetItem();
  FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( id );
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  if( ftd && ftd->GetKind() == FileTreeData::ftdkProject ) {
    if( ftd->GetProject() != pm->GetActiveProject() ) {
      pm->SetProject( ftd->GetProject(), false );
    }
    m_pTree->IsExpanded( id ) ? m_pTree->Collapse( id ) : m_pTree->Expand( id );
  } else if( ftd && ( ( ftd->GetKind() == FileTreeData::ftdkVirtualGroup )
                      || ( ftd->GetKind() == FileTreeData::ftdkVirtualFolder )
                      || ( ftd->GetKind() == FileTreeData::ftdkFolder ) ) ) {
    m_pTree->IsExpanded( id ) ? m_pTree->Collapse( id ) : m_pTree->Expand( id );
  } else if( !ftd && pm->GetWorkspace() ) {
    m_pTree->IsExpanded( m_TreeRoot ) ? m_pTree->Collapse( m_TreeRoot ) : m_pTree->Expand( m_TreeRoot );
  } else {
    DoOpenSelectedFile();
  }
}

void ProjectManagerUI::OnExecParameters( M_Code_unused wxCommandEvent& event ) {
  if( Manager::Get()->GetProjectManager()->GetActiveProject() ) {
    Manager::Get()->GetProjectManager()->GetActiveProject()->SelectTarget( -1, true );
  }
}

void ProjectManagerUI::OnRightClick( M_Code_unused wxCommandEvent& event ) {
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  if( !pm ) {
    return;
  }
  bool notCompilingProject = true;
  M_Code_Project *project = pm->GetActiveProject();
  if( project && project->GetCurrentlyCompilingTarget() ) {
    notCompilingProject = false;
  }
  wxMenu menu;
  if( pm->GetWorkspace() ) {
    menu.Append( idMenuTreeRenameWorkspace, _T( "重命名 工作空间..." ) );
    menu.Enable( idMenuTreeRenameWorkspace, notCompilingProject );
    menu.Append( idMenuTreeSaveWorkspace, _T( "保存" ) );
    menu.Enable( idMenuTreeSaveWorkspace, notCompilingProject );
    menu.Append( idMenuTreeSaveAsWorkspace, _T( "另存为" ) );
    menu.Enable( idMenuTreeSaveAsWorkspace, notCompilingProject );
    menu.Append( idMenuFindFile, _T( "查找文件" ) );
  }
  Manager::Get()->GetPluginManager()->AskPluginsForModuleMenu( mtProjectManager, &menu );
  menu.AppendCheckItem( idMenuViewCategorizePopup, _T( "文件分类" ) );
  menu.AppendCheckItem( idMenuViewUseFoldersPopup, _T( "显示虚拟文件夹" ) );
  menu.AppendCheckItem( idMenuViewHideFolderNamePopup, _T( "隐藏路径" ) );
  bool do_categorise = ( m_TreeVisualState & ptvsCategorize );
  bool do_use_folders = ( m_TreeVisualState & ptvsUseFolders );
  bool do_hide_folder_name = !do_use_folders && ( m_TreeVisualState & ptvsHideFolderName );
  menu.Check( idMenuViewCategorizePopup, do_categorise );
  menu.Check( idMenuViewUseFoldersPopup, do_use_folders );
  menu.Check( idMenuViewHideFolderNamePopup, do_hide_folder_name );
  menu.Enable( idMenuViewUseFoldersPopup, !do_hide_folder_name );
  menu.Enable( idMenuViewHideFolderNamePopup, !do_use_folders );
  menu.Append( idMenuViewFileMasks, _T( "编辑文件类别" ) );
  menu.Enable( idMenuViewFileMasks, notCompilingProject );
  if( pm->GetWorkspace() ) {
    menu.Append( idMenuTreeCloseWorkspace, _T( "关闭工作空间" ) );
    menu.Enable( idMenuTreeCloseWorkspace, notCompilingProject );
  }
  wxPoint pt = wxGetMousePosition();
  pt = m_pTree->ScreenToClient( pt );
  m_pTree->PopupMenu( &menu, pt );
}

void ProjectManagerUI::OnTreeItemRightClick( wxTreeEvent& event ) {
  if( Manager::Get()->GetProjectManager()->IsLoadingProject() ) {
    wxBell();
    return;
  }
  m_RightClickItem = event.GetItem();
  m_pTree->SelectItem( event.GetItem() );
  ShowMenu( event.GetItem(), event.GetPoint() );
  m_RightClickItem.Unset();
}

void ProjectManagerUI::OnRenameWorkspace( M_Code_unused wxCommandEvent& event ) {
  M_Code_Workspace* wkspc = Manager::Get()->GetProjectManager()->GetWorkspace();
  if( wkspc ) {
    wxString text = cbGetTextFromUser( _T( "请输入工作区的新名称:" ), _T( "重命名工作区" ), wkspc->GetTitle() );
    if( !text.IsEmpty() ) {
      wkspc->SetTitle( text );
      m_pTree->SetItemText( m_TreeRoot, wkspc->GetTitle() );
    }
  }
}

void ProjectManagerUI::OnSaveWorkspace( M_Code_unused wxCommandEvent& event ) {
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  if( pm->GetWorkspace() ) {
    pm->SaveWorkspace();
  }
}

void ProjectManagerUI::OnSaveAsWorkspace( M_Code_unused wxCommandEvent& event ) {
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  if( pm->GetWorkspace() ) {
    pm->SaveWorkspaceAs( _T( "" ) );
  }
}

void ProjectManagerUI::OnCloseWorkspace( M_Code_unused wxCommandEvent& event ) {
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  if( pm->GetWorkspace() ) {
    pm->CloseWorkspace();
  }
}

void ProjectManagerUI::OnSetActiveProject( wxCommandEvent& event ) {
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  ProjectsArray* pa = pm->GetProjects();
  if( event.GetId() == idMenuSetActiveProject ) {
    wxTreeItemId sel = GetTreeSelection();
    if( !sel.IsOk() ) {
      return;
    }
    FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( sel );
    if( !ftd ) {
      return;
    }
    pm->SetProject( ftd->GetProject(), false );
  } else if( event.GetId() == idMenuPriorProject ) {
    int index = pa->Index( pm->GetActiveProject() );
    if( index == wxNOT_FOUND ) {
      return;
    }
    --index;
    if( index < 0 ) {
      index = pa->GetCount() - 1;
    }
    pm->SetProject( pa->Item( index ), false );
  } else if( event.GetId() == idMenuNextProject ) {
    int index = pa->Index( pm->GetActiveProject() );
    if( index == wxNOT_FOUND ) {
      return;
    }
    ++index;
    if( index == ( int )pa->GetCount() ) {
      index = 0;
    }
    pm->SetProject( pa->Item( index ), false );
  } else if( event.GetId() == idMenuProjectUp ) {
    wxTreeItemId sel = GetTreeSelection();
    if( !sel.IsOk() ) {
      return;
    }
    if( FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( sel ) ) {
      MoveProjectUp( ftd->GetProject() );
    }
  } else if( event.GetId() == idMenuProjectDown ) {
    wxTreeItemId sel = GetTreeSelection();
    if( !sel.IsOk() ) {
      return;
    }
    if( FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( sel ) ) {
      MoveProjectDown( ftd->GetProject() );
    }
  }
}

void ProjectManagerUI::OnAddFilesToProjectRecursively( wxCommandEvent& event ) {
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  M_Code_Project* prj = nullptr;
  wxString basePath;
  if( event.GetId() == idMenuAddFilesRecursively ) {
    prj = pm->GetActiveProject();
    if( prj ) {
      basePath = prj->GetBasePath();
    }
  } else {
    wxTreeItemId sel = GetTreeSelection();
    if( !sel.IsOk() ) {
      return;
    }
    if( FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( sel ) ) {
      if( ( prj = ftd->GetProject() ) ) {
        basePath = ftd->GetFolder();
        if( !wxDirExists( basePath ) ) {
          basePath = prj->GetBasePath();
        }
      }
    }
  }
  if( !prj ) {
    return;
  }
  wxString dir = ChooseDirectory( m_pTree, _T( "递归添加文件..." ), basePath, wxEmptyString, false, false );
  if( dir.IsEmpty() ) {
    return;
  }
  wxArrayInt targets;
  if( prj->GetBuildTargetsCount() == 1 ) {
    targets.Add( 0 );
  }
  wxArrayString array;
  wxDir::GetAllFiles( dir, &array, wxEmptyString, wxDIR_FILES | wxDIR_DIRS );
  if( array.GetCount() == 0 ) {
    return;
  }
  unsigned int i = 0;
  while( i < array.GetCount() ) {
    if( wxDirExists( array[i] ) ||
        array[i].Contains( _T( "/.git/" ) ) ||
        array[i].Contains( _T( "\\.git\\" ) ) ||
        array[i].Contains( _T( "\\.hg\\" ) ) ||
        array[i].Contains( _T( "/.hg/" ) ) ||
        array[i].Contains( _T( "\\.svn\\" ) ) ||
        array[i].Contains( _T( "/.svn/" ) ) ||
        array[i].Contains( _T( "\\CVS\\" ) ) ||
        array[i].Contains( _T( "/CVS/" ) ) ||
        array[i].Lower().Matches( _T( "*.cbp" ) ) ) {
      array.RemoveAt( i );
    } else
    { ++i; }
  }
  wxString wild;
  const FilesGroupsAndMasks* fgam = pm->GetFilesGroupsAndMasks();
  for( unsigned fm_idx = 0; fm_idx < fgam->GetGroupsCount(); fm_idx++ ) {
    wild += fgam->GetFileMasks( fm_idx );
  }
  MultiSelectDlg dlg( nullptr, array, wild, _T( "选择要添加到项目中的文件:" ) );
  PlaceWindow( &dlg );
  if( dlg.ShowModal() != wxID_OK ) {
    return;
  }
  array = dlg.GetSelectedStrings();
  pm->AddMultipleFilesToProject( array, prj, targets );
  RebuildTree();
}

void ProjectManagerUI::OnAddFileToProject( wxCommandEvent& event ) {
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  M_Code_Project* prj = nullptr;
  wxString basePath;
  if( event.GetId() == idMenuAddFile ) {
    prj = pm->GetActiveProject();
    if( prj ) {
      basePath = prj->GetBasePath();
    }
  } else {
    wxTreeItemId sel = GetTreeSelection();
    if( !sel.IsOk() ) {
      return;
    }
    if( FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( sel ) ) {
      if( ( prj = ftd->GetProject() ) ) {
        basePath = ftd->GetFolder();
        if( !wxDirExists( basePath ) ) {
          basePath = prj->GetBasePath();
        }
      }
    }
  }
  if( !prj ) {
    return;
  }
  wxFileDialog dlg( Manager::Get()->GetAppWindow(), _T( "将文件添加到项目..." ), basePath, wxEmptyString,
                    FileFilters::GetFilterString(),
                    wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST | compatibility::wxHideReadonly );
  dlg.SetFilterIndex( FileFilters::GetIndexForFilterAll() );
  PlaceWindow( &dlg );
  if( dlg.ShowModal() == wxID_OK ) {
    wxArrayInt targets;
    if( prj->GetBuildTargetsCount() == 1 ) {
      targets.Add( 0 );
    }
    wxArrayString array;
    dlg.GetPaths( array );
    pm->AddMultipleFilesToProject( array, prj, targets );
    RebuildTree();
  }
}

namespace {
  void FindFiles( wxArrayString &resultFiles, wxTreeCtrl &tree, wxTreeItemId item ) {
    FileTreeData* ftd = static_cast<FileTreeData*>( tree.GetItemData( item ) );
    if( !ftd ) {
      return;
    }
    switch( ftd->GetKind() ) {
      case FileTreeData::ftdkFile:
        resultFiles.Add( ftd->GetProjectFile()->relativeFilename );
        break;
      case FileTreeData::ftdkFolder: {
        wxTreeItemIdValue cookie;
        wxTreeItemId i = tree.GetFirstChild( item, cookie );
        while( i.IsOk() ) {
          FindFiles( resultFiles, tree, i );
          i = tree.GetNextChild( item, cookie );
        }
      }
      break;
      case FileTreeData::ftdkUndefined:
      case FileTreeData::ftdkProject:
      case FileTreeData::ftdkVirtualGroup:
      case FileTreeData::ftdkVirtualFolder:
      default:
        for( FilesList::iterator it = ftd->GetProject()->GetFilesList().begin(); it != ftd->GetProject()->GetFilesList().end(); ++it ) {
          resultFiles.Add( ( ( ProjectFile* )*it )->relativeFilename );
        }
    }
  }
}

void ProjectManagerUI::OnRemoveFileFromProject( wxCommandEvent& event ) {
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  wxTreeItemId sel = GetTreeSelection();
  if( !sel.IsOk() ) {
    return;
  }
  FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( sel );
  if( !ftd ) {
    return;
  }
  M_Code_Project* prj = ftd->GetProject();
  if( !prj ) {
    return;
  }
  wxString oldpath = prj->GetCommonTopLevelPath();
  if( event.GetId() == idMenuRemoveFile ) {
    wxArrayString files;
    FindFiles( files, *m_pTree, sel );
    if( files.Count() == 0 ) {
      msging( _T( "此项目不包含任何要删除的文件." ), _T( "错误" ), wxICON_WARNING );
      return;
    }
    files.Sort();
    wxString msg;
    msg.Printf( _T( "选择要从中删除的文件 %s:" ), prj->GetTitle().c_str() );
    MultiSelectDlg dlg( nullptr, files, true, msg );
    PlaceWindow( &dlg );
    if( dlg.ShowModal() == wxID_OK ) {
      wxArrayInt indices = dlg.GetSelectedIndices();
      if( indices.GetCount() == 0 ) {
        return;
      }
      if( msging( _T( "从项目中删除这些文件?" ), _T( "审查Confirmation" ),
                  wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT ) != wxID_YES ) {
        return;
      }
      prj->BeginRemoveFiles();
      for( int i = ( int )indices.GetCount() - 1; i >= 0; --i ) {
        Manager::Get()->GetLogManager()->Log( F( _T( "移除索引 %d" ), indices[i] ) );
        if( ProjectFile* pf = prj->GetFileByFilename( files[indices[i]] ) ) {
          pm->RemoveFileFromProject( pf, prj );
        }
      }
      prj->CalculateCommonTopLevelPath();
      prj->EndRemoveFiles();
      RebuildTree();
    }
  } else if( event.GetId() == idMenuRemoveFilePopup ) {
    if( ProjectFile* pf = ftd->GetProjectFile() ) {
      prj->BeginRemoveFiles();
      pm->RemoveFileFromProject( pf, prj );
      prj->CalculateCommonTopLevelPath();
      if( prj->GetCommonTopLevelPath() == oldpath ) {
        m_pTree->Delete( sel );
      }
      prj->EndRemoveFiles();
      RebuildTree();
    }
  } else if( event.GetId() == idMenuRemoveFolderFilesPopup ) {
    if( msging( _T( "从项目中递归删除此文件夹下的所有文件?" ), _T( "审查" ), wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT ) != wxID_YES ) {
      return;
    }
    bool is_virtual = ftd->GetKind() == FileTreeData::ftdkVirtualFolder;
    if( is_virtual || ftd->GetKind() == FileTreeData::ftdkFolder ) {
      prj->BeginRemoveFiles();
      RemoveFilesRecursively( sel );
      prj->EndRemoveFiles();
    }
    prj->CalculateCommonTopLevelPath();
    if( prj->GetCommonTopLevelPath() == oldpath && !is_virtual ) {
      m_pTree->Delete( sel );
    } else if( is_virtual ) {
      ProjectVirtualFolderDeleted( prj, m_pTree, sel );
    }
    RebuildTree();
  }
}

void ProjectManagerUI::OnSaveProject( wxCommandEvent& ( event ) ) {
  wxTreeItemId sel = GetTreeSelection();
  if( !sel.IsOk() ) {
    return;
  }
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  if( FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( sel ) ) {
    if( M_Code_Project* prj = ftd->GetProject() ) {
      if( pm->IsLoadingProject() || prj->GetCurrentlyCompilingTarget() ) {
        wxBell();
      } else
      { pm->SaveProject( prj ); }
    }
  }
}

void ProjectManagerUI::OnCloseProject( wxCommandEvent& ( event ) ) {
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  if( pm->IsLoadingProject() ) {
    wxBell();
    return;
  }
  wxArrayTreeItemIds selections;
  int count = m_pTree->GetSelections( selections );
  if( count == 0 ) {
    return;
  }
  std::set<M_Code_Project*> projectsToClose;
  for( size_t ii = 0; ii < selections.GetCount(); ++ii ) {
    FileTreeData* ftd = reinterpret_cast<FileTreeData*>( m_pTree->GetItemData( selections[ii] ) );
    if( !ftd || ftd->GetKind() != FileTreeData::ftdkProject ) {
      continue;
    }
    M_Code_Project* prj = ftd->GetProject();
    if( prj ) {
      if( prj->GetCurrentlyCompilingTarget() ) {
        wxBell();
      } else
      { projectsToClose.insert( prj ); }
    }
  }
  for( std::set<M_Code_Project*>::iterator it = projectsToClose.begin(); it != projectsToClose.end(); ++it ) {
    pm->CloseProject( *it );
  }
  if( pm->GetProjects()->GetCount() > 0 && !pm->GetActiveProject() ) {
    pm->SetProject( pm->GetProjects()->Item( 0 ), false );
  }
  Manager::Get()->GetAppWindow()->Refresh();
}

void ProjectManagerUI::OnSaveFile( wxCommandEvent& ( event ) ) {
  wxTreeItemId sel = GetTreeSelection();
  if( !sel.IsOk() ) {
    return;
  }
  if( FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( sel ) ) {
    if( ProjectFile* pf = ftd->GetProjectFile() ) {
      Manager::Get()->GetEditorManager()->Save( pf->file.GetFullPath() );
    }
  }
}

void ProjectManagerUI::OnCloseFile( wxCommandEvent& ( event ) ) {
  wxTreeItemId sel = GetTreeSelection();
  if( !sel.IsOk() ) {
    return;
  }
  if( FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( sel ) ) {
    if( ProjectFile* pf = ftd->GetProjectFile() ) {
      Manager::Get()->GetEditorManager()->Close( pf->file.GetFullPath() );
    }
  }
}

void ProjectManagerUI::OnOpenFile( wxCommandEvent& ( event ) ) {
  DoOpenSelectedFile();
}

void ProjectManagerUI::OnOpenFolderFiles( wxCommandEvent& event ) {
  wxTreeItemId sel = GetTreeSelection();
  FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( sel );
  if( !ftd ) {
    return;
  }
  if( msging( _T( "从项目中递归打开此文件夹下的所有文件?" ), _T( "确认" ),
              wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT ) != wxID_YES ) {
    return;
  }
  if( ftd->GetKind() == FileTreeData::ftdkFolder
      || ftd->GetKind() == FileTreeData::ftdkVirtualFolder ) {
    OpenFilesRecursively( sel );
  }
  event.Skip();
}

void ProjectManagerUI::OnOpenWith( wxCommandEvent& event ) {
  wxTreeItemId sel = GetTreeSelection();
  if( !sel.IsOk() ) {
    return;
  }
  FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( sel );
  if( !ftd ) {
    return;
  }
  if( ProjectFile* pf = ftd->GetProjectFile() ) {
    wxString filename = pf->file.GetFullPath();
    if( event.GetId() == idOpenWithInternal ) {
      if( M_Code_Editor* ed = Manager::Get()->GetEditorManager()->Open( filename ) ) {
        ed->SetProjectFile( pf );
        ed->Show( true );
        return;
      }
    } else {
      PluginsArray mimes = Manager::Get()->GetPluginManager()->GetMimeOffers();
      cbMimePlugin* plugin = ( cbMimePlugin* )mimes[event.GetId() - idOpenWith[0]];
      if( plugin && plugin->OpenFile( filename ) == 0 ) {
        return;
      }
    }
    wxString msg;
    msg.Printf( _T( "打开失败 '%s'." ), filename.c_str() );
    Manager::Get()->GetLogManager()->LogError( msg );
  }
}

void ProjectManagerUI::OnNotes( wxCommandEvent& ( event ) ) {
  if( M_Code_Project* prj = Manager::Get()->GetProjectManager()->GetActiveProject() ) {
    prj->ShowNotes( false, true );
  }
}

void ProjectManagerUI::OnProperties( wxCommandEvent& event ) {
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  M_Code_Project* activePrj = pm->GetActiveProject();
  if( event.GetId() == idMenuProjectProperties ) {
    wxString backupTitle = activePrj ? activePrj->GetTitle() : _T( "" );
    if( ProjectShowOptions( activePrj ) ) {
      pm->SetProject( activePrj, backupTitle != activePrj->GetTitle() );
    }
  } else if( event.GetId() == idMenuTreeProjectProperties ) {
    wxTreeItemId sel = GetTreeSelection();
    if( !sel.IsOk() ) {
      return;
    }
    FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( sel );
    M_Code_Project* prj = ftd ? ftd->GetProject() : activePrj;
    wxString backupTitle = prj ? prj->GetTitle() : _T( "" );
    if( ProjectShowOptions( prj ) && prj == activePrj ) {
      pm->SetProject( prj, backupTitle != prj->GetTitle() );
    }
    wxString newTitle = prj->GetTitle();
    if( backupTitle != newTitle ) {
      M_Code_AuiNotebook* nb = Manager::Get()->GetEditorManager()->GetNotebook();
      if( nb ) {
        wxString toolTip;
        for( size_t i = 0; i < nb->GetPageCount(); ++i ) {
          toolTip = nb->GetPageToolTip( i );
          if( toolTip.EndsWith( _T( "Project: " ) + backupTitle ) ) {
            toolTip.Replace( _T( "Project: " ) + backupTitle, _T( "Project: " ) + newTitle );
            nb->SetPageToolTip( i, toolTip );
          }
        }
      }
    }
  } else if( event.GetId() == idMenuTreeFileProperties ) {
    wxTreeItemId sel = GetTreeSelection();
    if( !sel.IsOk() ) {
      return;
    }
    FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( sel );
    M_Code_Project* prj = ftd ? ftd->GetProject() : activePrj;
    if( prj ) {
      if( ftd && ftd->GetFileIndex() != -1 ) {
        if( ProjectFile* pf = ftd->GetProjectFile() ) {
          pf->ShowOptions( Manager::Get()->GetAppWindow() );
        }
      }
    }
  } else {
    if( M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor() ) {
      if( ProjectFile* pf = ed->GetProjectFile() ) {
        pf->ShowOptions( Manager::Get()->GetAppWindow() );
      } else {
        ProjectFileOptionsDlg dlg( Manager::Get()->GetAppWindow(), ed->GetFilename() ,nullptr);
        PlaceWindow( &dlg );
        dlg.ShowModal();
      }
    }
  }
}

template<typename Func>
static void applyFileOptionChange( std::set<M_Code_Project*> &modified, wxTreeCtrl &tree, Func func ) {
  wxArrayTreeItemIds selected;
  size_t count = tree.GetSelections( selected );
  for( size_t ii = 0; ii < count; ++ii ) {
    wxTreeItemId id = selected[ii];
    if( !id.IsOk() ) {
      continue;
    }
    FileTreeData* ftd = ( FileTreeData* )tree.GetItemData( id );
    if( !ftd || ftd->GetKind() != FileTreeData::ftdkFile ) {
      continue;
    }
    ProjectFile* pf = ftd->GetProjectFile();
    if( pf && func( *pf ) ) {
      if( pf->GetParentProject() ) {
        modified.insert( pf->GetParentProject() );
      }
    }
  }
}

void ProjectManagerUI::OnFileOptions( wxCommandEvent &event ) {
  std::set<M_Code_Project*> modified;
  if( event.GetId() == idMenuTreeOptionsCompile ) {
    const bool checked = event.IsChecked();
    applyFileOptionChange( modified, *m_pTree, [checked]( ProjectFile & pf ) -> bool {
      if( pf.compile != checked ) {
        pf.compile = checked;
        return true;
      } else
      { return false; }
    } );
  } else if( event.GetId() == idMenuTreeOptionsLink ) {
    const bool checked = event.IsChecked();
    applyFileOptionChange( modified, *m_pTree, [checked]( ProjectFile & pf ) -> bool {
      if( pf.link != checked ) {
        pf.link = checked;
        return true;
      } else
      { return false; }
    } );
  } else if( event.GetId() == idMenuTreeOptionsEnableBoth || event.GetId() == idMenuTreeOptionsDisableBoth ) {
    const bool newValue = ( event.GetId() == idMenuTreeOptionsEnableBoth );
    applyFileOptionChange( modified, *m_pTree, [newValue]( ProjectFile & pf ) -> bool {
      pf.compile = pf.link = newValue;
      return true;
    } );
  }
  for( std::set<M_Code_Project*>::iterator it = modified.begin(); it != modified.end(); ++it ) {
    ( *it )->SetModified( true );
  }
  if( !modified.empty() ) {
    RebuildTree();
  }
  event.Skip();
}

struct ProjectFileRelativePathCmp {
    ProjectFileRelativePathCmp( M_Code_Project* pActiveProject ) : m_pActiveProject( pActiveProject ) {}
    bool operator()( ProjectFile* pf1, ProjectFile* pf2 ) {
      if( pf1->GetParentProject() == m_pActiveProject && pf2->GetParentProject() != m_pActiveProject ) {
        return true;
      } else if( pf1->GetParentProject() != m_pActiveProject && pf2->GetParentProject() == m_pActiveProject ) {
        return false;
      } else {
        int relCmp = pf1->relativeFilename.Cmp( pf2->relativeFilename );
        if( relCmp == 0 ) {
          return pf1 < pf2;
        } else
        { return relCmp < 0; }
      }
    }
  private:
    M_Code_Project* m_pActiveProject;
};

struct cbStringHash {
  size_t operator()( const wxString& s ) const {
    return std::hash<std::wstring>()( s.wc_str() );
  }
};

void ProjectManagerUI::OnGotoFile( M_Code_unused wxCommandEvent& event ) {
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  M_Code_Project* activePrj = pm->GetActiveProject();
  if( !activePrj ) {
    Manager::Get()->GetLogManager()->LogError( _T( "没有活动项目!" ) );
    return;
  }
  ProjectsArray* pa = pm->GetProjects();
  std::unordered_map<wxString, ProjectFile*, cbStringHash> uniqueAbsPathFiles;
  for( size_t prjIdx = 0; prjIdx < pa->GetCount(); ++prjIdx ) {
    M_Code_Project* prj = ( *pa )[prjIdx];
    if( !prj ) {
      continue;
    }
    for( FilesList::iterator it = prj->GetFilesList().begin(); it != prj->GetFilesList().end(); ++it ) {
      ProjectFile *projectFile = *it;
      uniqueAbsPathFiles.insert( {projectFile->file.GetFullPath(), projectFile} );
    }
  }
  typedef std::vector<ProjectFile*> VProjectFiles;
  VProjectFiles pfiles;
  if( !uniqueAbsPathFiles.empty() ) {
    pfiles.reserve( uniqueAbsPathFiles.size() );
    for( const auto &pf : uniqueAbsPathFiles ) {
      pfiles.push_back( pf.second );
    }
    std::sort( pfiles.begin(), pfiles.end(), ProjectFileRelativePathCmp( activePrj ) );
  }
  struct Iterator : IncrementalSelectIteratorIndexed {
      Iterator( VProjectFiles &pfiles, bool showProject ) :
        m_pfiles( pfiles ), m_ShowProject( showProject ), m_ColumnWidth( 300 ) {
      }
      int GetTotalCount() const override {
        return m_pfiles.size();
      }
      const wxString& GetItemFilterString( int index ) const override {
        return m_pfiles[index]->relativeFilename;
      }
      wxString GetDisplayText( int index, int column ) const override {
        ProjectFile* pf = m_pfiles[m_indices[index]];
        return MakeDisplayName( *pf );
      }
      int GetColumnWidth( int column ) const override {
        return m_ColumnWidth;
      }
      void CalcColumnWidth( wxListCtrl &list ) override {
        int length = 0;
        ProjectFile *pfLongest = nullptr;
        for( const auto &pf : m_pfiles ) {
          int pfLength = pf->relativeFilename.length();
          if( m_ShowProject ) {
            pfLength += pf->GetParentProject()->GetTitle().length() + 3;
          }
          if( pfLength > length ) {
            length = pfLength;
            pfLongest = pf;
          }
        }
        if( pfLongest ) {
          const wxString &longestString = MakeDisplayName( *pfLongest );
          int yTemp;
          list.GetTextExtent( longestString, &m_ColumnWidth, &yTemp );
          m_ColumnWidth += 50;
        } else
        { m_ColumnWidth = 300; }
      }
    private:
      wxString MakeDisplayName( ProjectFile &pf ) const {
        if( m_ShowProject ) {
          return pf.relativeFilename + _T( " (" ) + pf.GetParentProject()->GetTitle() + _T( ")" );
        } else
        { return pf.relativeFilename; }
      }
    private:
      const VProjectFiles &m_pfiles;
      wxString temp;
      bool m_ShowProject;
      int m_ColumnWidth;
  };
  Iterator iterator( pfiles, ( pa->GetCount() > 1 ) );
  GotoFile dlg( Manager::Get()->GetAppWindow(), &iterator, _T( "选择文件..." ), _T( "选择要打开的文件:" ) );
  PlaceWindow( &dlg );
  if( dlg.ShowModal() == wxID_OK ) {
    int selection = dlg.GetSelection();
    if( selection >= 0 && selection < int( pfiles.size() ) ) {
      DoOpenFile( pfiles[selection], pfiles[selection]->file.GetFullPath() );
    }
  }
}

void ProjectManagerUI::OnViewCategorize( wxCommandEvent& event ) {
  bool do_categorise = event.IsChecked();
  if( do_categorise ) {
    m_TreeVisualState |=  ptvsCategorize;
  } else {
    m_TreeVisualState &= ~ptvsCategorize;
  }
  Manager::Get()->GetAppFrame()->GetMenuBar()->Check( idMenuViewCategorize, do_categorise );
  Manager::Get()->GetConfigManager( _T( "project_manager" ) )->Write( _T( "/categorize_tree" ), do_categorise );
  RebuildTree();
}

void ProjectManagerUI::OnViewUseFolders( wxCommandEvent& event ) {
  bool do_use_folders = event.IsChecked();
  if( do_use_folders ) {
    m_TreeVisualState |=  ptvsUseFolders;
  } else {
    m_TreeVisualState &= ~ptvsUseFolders;
  }
  Manager::Get()->GetAppFrame()->GetMenuBar()->Check( idMenuViewUseFolders, do_use_folders );
  Manager::Get()->GetAppFrame()->GetMenuBar()->Enable( idMenuViewHideFolderName, !do_use_folders );
  Manager::Get()->GetConfigManager( _T( "project_manager" ) )->Write( _T( "/use_folders" ), do_use_folders );
  if( do_use_folders ) {
    m_TreeVisualState &= ~ptvsHideFolderName;
    Manager::Get()->GetAppFrame()->GetMenuBar()->Check( idMenuViewHideFolderName, false );
    Manager::Get()->GetConfigManager( _T( "project_manager" ) )->Write( _T( "/hide_folder_name" ), false );
  }
  RebuildTree();
}

void ProjectManagerUI::OnViewHideFolderName( wxCommandEvent& event ) {
  bool do_hide_folder_name = event.IsChecked();
  if( do_hide_folder_name ) {
    m_TreeVisualState |=  ptvsHideFolderName;
  } else {
    m_TreeVisualState &= ~ptvsHideFolderName;
  }
  Manager::Get()->GetAppFrame()->GetMenuBar()->Check( idMenuViewHideFolderName, do_hide_folder_name );
  Manager::Get()->GetAppFrame()->GetMenuBar()->Enable( idMenuViewUseFolders, !do_hide_folder_name );
  Manager::Get()->GetConfigManager( _T( "project_manager" ) )->Write( _T( "/hide_folder_name" ), do_hide_folder_name );
  if( do_hide_folder_name ) {
    m_TreeVisualState &= ~ptvsUseFolders;
    Manager::Get()->GetAppFrame()->GetMenuBar()->Check( idMenuViewUseFolders, false );
    Manager::Get()->GetConfigManager( _T( "project_manager" ) )->Write( _T( "/use_folders" ), false );
  }
  RebuildTree();
}

void ProjectManagerUI::OnViewFileMasks( M_Code_unused wxCommandEvent& event ) {
  FilesGroupsAndMasks* fgam = Manager::Get()->GetProjectManager()->GetFilesGroupsAndMasks();
  ProjectsFileMasksDlg dlg( Manager::Get()->GetAppWindow(), fgam );
  PlaceWindow( &dlg );
  if( dlg.ShowModal() == wxID_OK ) {
    fgam->Save();
    RebuildTree();
  }
}

wxArrayString ProjectManagerUI::ListNodes( wxTreeItemId node ) const {
  wxArrayString nodes;
  wxTreeItemIdValue cookie;
  wxTreeItemId item = m_pTree->GetFirstChild( node, cookie );
  while( item.IsOk() ) {
    nodes.Add( m_pTree->GetItemText( item ) );
    if( m_pTree->ItemHasChildren( item ) ) {
      const wxArrayString& children = ListNodes( item );
      const wxString parent = nodes.Last();
      for( size_t i = 0; i < children.GetCount(); ++i ) {
        nodes.Add( parent + _T( "/" ) + children[i] );
      }
    }
    item = m_pTree->GetNextChild( node, cookie );
  }
  return nodes;
}

void ProjectManagerUI::OnFindFile( M_Code_unused wxCommandEvent& event ) {
  wxTreeItemId sel = GetTreeSelection();
  if( !sel.IsOk() ) {
    return;
  }
  wxArrayString files = ListNodes( sel );
  if( files.IsEmpty() ) {
    return;
  }
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  ConfigManagerContainer::StringToStringMap fileNameMap;
  if( pm->GetWorkspace() && !( FileTreeData* )m_pTree->GetItemData( sel ) ) {
    for( size_t i = 0; i < pm->GetProjects()->GetCount(); ++i ) {
      const M_Code_Project* prj = pm->GetProjects()->Item( i );
      const wxFileName file( prj->GetFilename() );
      files.Add( file.GetFullName() );
      fileNameMap[file.GetFullName()] = prj->GetTitle();
    }
  }
  struct Iterator : IncrementalSelectIteratorIndexed {
      Iterator( const wxArrayString &files ) : m_files( files ), m_ColumnWidth( 300 ) {
      }
      int GetTotalCount() const override {
        return m_files.size();
      }
      const wxString& GetItemFilterString( int index ) const override {
        return m_files[index];
      }
      wxString GetDisplayText( int index, int column ) const override {
        return m_files[m_indices[index]];
      }
      int GetColumnWidth( int column ) const override {
        return m_ColumnWidth;
      }
      void CalcColumnWidth( wxListCtrl &list ) override {
        int index = -1;
        size_t length = 0;
        for( size_t ii = 0; ii < m_files.size(); ++ii ) {
          size_t itemLength = m_files[ii].length();
          if( itemLength > length ) {
            index = ii;
            length = itemLength;
          }
        }
        if( index >= 0 && index < int( m_files.size() ) ) {
          int yTemp;
          list.GetTextExtent( m_files[index], &m_ColumnWidth, &yTemp );
          m_ColumnWidth += 50;
        } else
        { m_ColumnWidth = 300; }
      }
    private:
      const wxArrayString &m_files;
      int m_ColumnWidth;
  };
  Iterator iter( files );
  GotoFile dlg( Manager::Get()->GetAppWindow(), &iter, _T( "查找文件..." ), _T( "请输入正在搜索的文件名:" ) );
  ConfigManager *cfg = Manager::Get()->GetConfigManager( _T( "project_manager" ) );
  wxCheckBox *chkOpen = new wxCheckBox( &dlg, wxID_ANY, _T( "打开文件" ) );
  chkOpen->SetValue( cfg->ReadBool( _T( "/find_file_open" ), false ) );
  dlg.AddControlBelowList( chkOpen );
  PlaceWindow( &dlg );
  if( dlg.ShowModal() != wxID_OK ) {
    return;
  }
  const long selection = dlg.GetSelection();
  if( selection == wxNOT_FOUND ) {
    return;
  }
  wxString file = files[selection];
  ConfigManagerContainer::StringToStringMap::iterator it = fileNameMap.find( file );
  if( it != fileNameMap.end() ) {
    file = it->second;
  }
  wxTreeItemIdValue cookie;
  wxTreeItemId item = m_pTree->GetFirstChild( sel, cookie );
  while( item.IsOk() ) {
    if( m_pTree->GetItemText( item ) == file ) {
      break;
    } else if( file.StartsWith( m_pTree->GetItemText( item ) + _T( "/" ) ) ) {
      file = file.Mid( m_pTree->GetItemText( item ).Length() + 1 );
      sel  = item;
      item = m_pTree->GetFirstChild( sel, cookie );
    } else
    { item = m_pTree->GetNextChild( sel, cookie ); }
  }
  if( item.IsOk() ) {
    m_pTree->UnselectAll();
    m_pTree->SelectItem( item );
    const FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( item );
    if( ftd && chkOpen->IsChecked() ) {
      if( ProjectFile* pf = ftd->GetProjectFile() ) {
        DoOpenFile( pf, pf->file.GetFullPath() );
      } else if( ftd->GetKind() == FileTreeData::ftdkProject
                 && ftd->GetProject() ) {
        pm->SetProject( ftd->GetProject(), false );
      }
    }
    cfg->Write( _T( "/find_file_open" ), chkOpen->IsChecked() );
  }
}

void ProjectManagerUI::OnAddVirtualFolder( M_Code_unused wxCommandEvent& event ) {
  wxString fld = cbGetTextFromUser( _T( "输入新的虚拟文件夹路径:" ), _T( "新建虚拟文件夹" ) );
  if( fld.IsEmpty() ) {
    return;
  }
  wxTreeItemId sel = GetTreeSelection();
  if( !sel.IsOk() ) {
    return;
  }
  FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( sel );
  if( !ftd ) {
    return;
  }
  M_Code_Project* prj = ftd->GetProject();
  if( !prj ) {
    return;
  }
  ProjectVirtualFolderAdded( prj, m_pTree, sel, fld );
}

void ProjectManagerUI::OnDeleteVirtualFolder( M_Code_unused wxCommandEvent& event ) {
  wxTreeItemId sel = GetTreeSelection();
  if( !sel.IsOk() ) {
    return;
  }
  FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( sel );
  if( !ftd ) {
    return;
  }
  M_Code_Project* prj = ftd->GetProject();
  if( !prj ) {
    return;
  }
  ProjectVirtualFolderDeleted( prj, m_pTree, sel );
  RebuildTree();
}

void ProjectManagerUI::OnRenameVirtualFolder( wxCommandEvent& event ) {
  wxTreeItemId sel = GetTreeSelection();
  if( !sel.IsOk() ) {
    return;
  }
  FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( sel );
  if( !ftd ) {
    return;
  }
  M_Code_Project* prj = ftd->GetProject();
  if( !prj ) {
    return;
  }
  wxString oldName = ftd->GetFolder();
  if( oldName.EndsWith( _T( "/" ) ) ) {
    oldName.RemoveLast( 1 );
  }
  wxTextEntryDialog dlg( Manager::Get()->GetAppWindow(), _T( "输入虚拟文件夹的新名称:" ),
                         _T( "重命名虚拟文件夹" ), oldName, wxOK | wxCANCEL | wxCENTRE );
  if( dlg.ShowModal() == wxID_OK ) {
    ProjectVirtualFolderRenamed( prj, m_pTree, sel, dlg.GetValue() );
    RebuildTree();
  }
}

void ProjectManagerUI::OnBeginEditNode( wxTreeEvent& event ) {
  wxTreeItemId id = event.GetItem();
  if( !id.IsOk() ) {
    event.Veto();
    return;
  }
  FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( id );
  if( !ftd ) {
    event.Veto();
    return;
  }
  if( ftd->GetKind() != FileTreeData::ftdkVirtualFolder ) {
    event.Veto();
  }
}

void ProjectManagerUI::OnEndEditNode( wxTreeEvent& event ) {
  FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( event.GetItem() );
  if( !ftd ) {
    event.Veto();
    return;
  }
  M_Code_Project* prj = ftd->GetProject();
  if( !prj ) {
    event.Veto();
    return;
  }
  if( !ProjectVirtualFolderRenamed( prj, m_pTree, event.GetItem(), event.GetLabel() ) ) {
    event.Veto();
  }
}

void ProjectManagerUI::OnUpdateUI( wxUpdateUIEvent& event ) {
  if( event.GetId() == idMenuFileProperties ) {
    EditorManager *editorManager = Manager::Get()->GetEditorManager();
    bool enableProperties;
    if( editorManager ) {
      EditorBase *editor = editorManager->GetActiveEditor();
      enableProperties = ( editor );
      if( enableProperties ) {
        enableProperties = !cbHasRunningCompilers( Manager::Get()->GetPluginManager() );
      }
    } else
    { enableProperties = false; }
    event.Enable( enableProperties );
  } else if( event.GetId() == idMenuProjectProperties || event.GetId() == idMenuAddFile
             || event.GetId() == idMenuAddFilesRecursively || event.GetId() == idMenuRemoveFile
             || event.GetId() == idMenuProjectTreeProps || event.GetId() == idMenuAddVirtualFolder
             || event.GetId() == idMenuDeleteVirtualFolder ) {
    ProjectManager *projectManager = Manager::Get()->GetProjectManager();
    if( !projectManager || ( projectManager->GetIsRunning() != nullptr ) ) {
      event.Enable( false );
    } else {
      M_Code_Project *project = projectManager->GetActiveProject();
      if( !project ) {
        event.Enable( false );
      } else {
        bool enable = !cbHasRunningCompilers( Manager::Get()->GetPluginManager() );
        event.Enable( enable );
      }
    }
  } else {
    event.Skip();
  }
}

void ProjectManagerUI::OnIdle( wxIdleEvent& event ) {
  event.Skip();
}

void ProjectManagerUI::OnRenameFile( M_Code_unused wxCommandEvent& event ) {
  wxTreeItemId sel = GetTreeSelection();
  if( !sel.IsOk() ) {
    return;
  }
  FileTreeData* ftd = ( FileTreeData* )m_pTree->GetItemData( sel );
  if( !ftd ) {
    return;
  }
  M_Code_Project* prj = ftd->GetProject();
  if( !prj ) {
    return;
  }
  if( ftd->GetProjectFile()->AutoGeneratedBy() ) {
    msging( _T( "无法重命名文件因为它是自动生成的..." ), _T( "错误" ) );
    return;
  }
  wxString path = ftd->GetProjectFile()->file.GetPath( wxPATH_GET_VOLUME | wxPATH_GET_SEPARATOR );
  wxString name = ftd->GetProjectFile()->file.GetFullName();
  wxTextEntryDialog dlg( Manager::Get()->GetAppWindow(), _T( "请输入新名称:" ), _T( "重命名文件" ), name, wxOK | wxCANCEL | wxCENTRE );
  PlaceWindow( &dlg );
  if( dlg.ShowModal() == wxID_OK ) {
    wxFileName fn( dlg.GetValue() );
    wxString new_name = fn.GetFullName();
    if( name != new_name ) {
      if( !wxRenameFile( path + name, path + new_name, false ) ) {
        wxBell();
        return;
      }
      if( ProjectFile* pf = ftd->GetProjectFile() ) {
        pf->Rename( new_name );
        RebuildTree();
      }
    }
  }
}

void ProjectManagerUI::OnKeyDown( wxTreeEvent& event ) {
  const wxKeyEvent& key_event = event.GetKeyEvent();
  M_Code_Project* prj = Manager::Get()->GetProjectManager()->GetActiveProject();
  if( prj && ( prj->GetCurrentlyCompilingTarget() == nullptr ) && ( key_event.GetKeyCode() == WXK_DELETE
      || key_event.GetKeyCode() == WXK_NUMPAD_DELETE ) ) {
    m_DraggingSelection.Clear();
    wxCommandEvent command( 0, idMenuRemoveFilePopup );
    OnRemoveFileFromProject( command );
  } else {
    event.Skip();
  }
}

void ProjectManagerUI::MoveProjectUp( M_Code_Project* project, bool warpAround ) {
  if( !project ) {
    return;
  }
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  ProjectsArray* pa = pm->GetProjects();
  int idx = pa->Index( project );
  if( idx == wxNOT_FOUND ) {
    return;
  }
  if( idx == 0 ) {
    if( !warpAround ) {
      return;
    } else
    { idx = pa->Count(); }
  }
  pa->RemoveAt( idx-- );
  pa->Insert( project, idx );
  RebuildTree();
  if( pm->GetWorkspace() ) {
    pm->GetWorkspace()->SetModified( true );
  }
  wxTreeItemId itemId = project->GetProjectNode();
  cbAssert( itemId.IsOk() );
  m_pTree->SelectItem( itemId );
}

void ProjectManagerUI::MoveProjectDown( M_Code_Project* project, bool warpAround ) {
  if( !project ) {
    return;
  }
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  ProjectsArray* pa = pm->GetProjects();
  int idx = pa->Index( project );
  if( idx == wxNOT_FOUND ) {
    return;
  }
  if( idx == ( int )pa->Count() - 1 ) {
    if( !warpAround ) {
      return;
    } else
    { idx = 0; }
  }
  pa->RemoveAt( idx++ );
  pa->Insert( project, idx );
  RebuildTree();
  if( pm->GetWorkspace() ) {
    pm->GetWorkspace()->SetModified( true );
  }
  wxTreeItemId itemId = project->GetProjectNode();
  cbAssert( itemId.IsOk() );
  m_pTree->SelectItem( itemId );
}


bool ProjectManagerUI::QueryCloseAllProjects() {
  if( !Manager::Get()->GetEditorManager()->QueryCloseAll() ) {
    return false;
  }
  ProjectsArray* pa = Manager::Get()->GetProjectManager()->GetProjects();
  for( size_t i = 0; i < pa->GetCount(); ++i ) {
    if( !QueryCloseProject( ( *pa )[i], true ) ) {
      return false;
    }
  }
  return true;
}

bool ProjectManagerUI::QueryCloseProject( M_Code_Project* proj, bool dontsavefiles ) {
  if( !proj ) {
    return true;
  }
  if( proj->GetCurrentlyCompilingTarget() ) {
    return false;
  }
  if( !dontsavefiles ) {
    if( !proj->QueryCloseAllFiles() ) {
      return false;
    }
  }
  if( proj->GetModified() && !Manager::IsBatchBuild() ) {
    if( !proj->Save() ) {
      return false;
    }
  }
  return true;
}

bool ProjectManagerUI::QueryCloseWorkspace() {
  M_Code_Workspace* wkspc = Manager::Get()->GetProjectManager()->GetWorkspace();
  if( !wkspc ) {
    return true;
  }
  bool blankWorkspace = Manager::Get()->GetConfigManager( _T( "app" ) )->ReadBool( _T( "/environment/blank_workspace" ), true );
  if( !( wkspc->IsDefault() && blankWorkspace ) ) {
    wkspc->SaveLayout();
    if( wkspc->GetModified() ) {
      wxString msg;
      msg.Printf( _T( "工作空间 '%s' 被修改保存更改?" ), wkspc->GetTitle().c_str() );
      switch( msging( msg, _T( "保存工作区" ), wxYES_NO | wxCANCEL | wxICON_QUESTION ) ) {
        case wxID_YES:
          Manager::Get()->GetProjectManager()->SaveWorkspace();
          break;
        case wxID_CANCEL:
          return false;
        default:
          break;
      }
    }
  }
  if( !QueryCloseAllProjects() ) {
    return false;
  }
  return true;
}

int ProjectManagerUI::AskForBuildTargetIndex( M_Code_Project* project ) {
  M_Code_Project* prj = project;
  if( !prj ) {
    prj = Manager::Get()->GetProjectManager()->GetActiveProject();
  }
  if( !prj ) {
    return -1;
  }
  wxArrayString array;
  int count = prj->GetBuildTargetsCount();
  for( int i = 0; i < count; ++i ) {
    array.Add( prj->GetBuildTarget( i )->GetTitle() );
  }
  int target = cbGetSingleChoiceIndex( _T( "选择目标:" ), _T( "项目目标" ), array, m_pTree, wxSize( 300, 400 ) );
  return target;
}

wxArrayInt ProjectManagerUI::AskForMultiBuildTargetIndex( M_Code_Project* project ) {
  wxArrayInt indices;
  M_Code_Project* prj = project;
  if( !prj ) {
    prj = Manager::Get()->GetProjectManager()->GetActiveProject();
  }
  if( !prj ) {
    return indices;
  }
  wxArrayString array;
  int count = prj->GetBuildTargetsCount();
  for( int i = 0; i < count; ++i ) {
    array.Add( prj->GetBuildTarget( i )->GetTitle() );
  }
  MultiSelectDlg dlg( nullptr, array, false, _T( "选择此文件应属于的目标:" ) );
  PlaceWindow( &dlg );
  if( dlg.ShowModal() == wxID_OK ) {
    indices = dlg.GetSelectedIndices();
  }
  return indices;
}

void ProjectManagerUI::ConfigureProjectDependencies( M_Code_Project* base ) {
  ProjectDepsDlg dlg( Manager::Get()->GetAppWindow(), base );
  PlaceWindow( &dlg );
  dlg.ShowModal();
}

void ProjectManagerUI::CheckForExternallyModifiedProjects() {
  if( m_isCheckingForExternallyModifiedProjects ) {
    return;
  }
  m_isCheckingForExternallyModifiedProjects = true;
  ProjectManager* pm = Manager::Get()->GetProjectManager();
  if( ProjectsArray* pa = pm->GetProjects() ) {
    bool reloadAll = false;
    std::vector<M_Code_Project*> ProjectPointers;
    for( unsigned int idxProject = 0; idxProject < pa->Count(); ++idxProject ) {
      ProjectPointers.push_back( pa->Item( idxProject ) );
    }
    for( unsigned int idxProject = 0; idxProject < ProjectPointers.size(); ++idxProject ) {
      M_Code_Project* prj = ProjectPointers[idxProject];
      wxFileName fname( prj->GetFilename() );
      wxDateTime last = fname.GetModificationTime();
      if( last.IsLaterThan( prj->GetLastModificationTime() ) ) {
        int ret = -1;
        if( !reloadAll ) {
          Manager::Get()->GetLogManager()->Log( prj->GetFilename() );
          wxString msg;
          msg.Printf( _T( "项目 %s 在IDE外修改...\n是否要重新加载（将丢失所有未保存的工作）?" ), prj->GetFilename().c_str() );
          ConfirmReplaceDlg dlg( Manager::Get()->GetAppWindow(), false, msg );
          dlg.SetTitle( _T( "重载工程?" ) );
          PlaceWindow( &dlg );
          wxWindow* win = wxWindow::GetCapture();
          if( win ) {
            win->ReleaseMouse();
          }
          ret = dlg.ShowModal();
          reloadAll = ret == crAll;
        }
        if( reloadAll || ret == crYes ) {
          pm->ReloadProject( prj );
        } else if( ret == crCancel ) {
          break;
        } else if( ret == crNo ) {
          prj->Touch();
        }
      }
    }
  }
  m_isCheckingForExternallyModifiedProjects = false;
}

namespace {
  static void ProjectTreeSortChildrenRecursive( M_Code_TreeCtrl* tree, const wxTreeItemId& parent ) {
    if( !tree->ItemHasChildren( parent ) ) {
      return;
    }
    tree->SortChildren( parent );
    wxTreeItemIdValue cookie = nullptr;
    wxTreeItemId current = tree->GetFirstChild( parent, cookie );
    while( current ) {
      ProjectTreeSortChildrenRecursive( tree, current );
      current = tree->GetNextChild( parent, cookie );
    }
  }
  static wxString GetRelativeFolderPath( wxTreeCtrl* tree, wxTreeItemId parent ) {
    wxString fld;
    while( parent.IsOk() ) {
      FileTreeData* ftd = ( FileTreeData* )tree->GetItemData( parent );
      if( !ftd
          || ( ( ftd->GetKind() != FileTreeData::ftdkFolder )
               && ( ftd->GetKind() != FileTreeData::ftdkVirtualFolder ) ) ) {
        break;
      }
      fld.Prepend( tree->GetItemText( parent ) + wxFILE_SEP_PATH );
      parent = tree->GetItemParent( parent );
    }
    return fld;
  }
  wxTreeItemId ProjectFindNodeToInsertAfter( wxTreeCtrl* tree, const wxString& text, const wxTreeItemId& parent, bool in_folders ) {
    wxTreeItemId result;
    if( tree && parent.IsOk() ) {
      wxTreeItemIdValue cookie = nullptr;
      int fldIdx = cbProjectTreeImages::FolderIconIndex();
      int vfldIdx = cbProjectTreeImages::VirtualFolderIconIndex();
      wxTreeItemId last;
      wxTreeItemId child = tree->GetFirstChild( parent, cookie );
      while( child ) {
        bool is_folder = tree->GetItemImage( child ) == fldIdx || tree->GetItemImage( child ) == vfldIdx;
        if( in_folders ) {
          if( !is_folder || text.CmpNoCase( tree->GetItemText( child ) ) < 0 ) {
            result = last;
            break;
          }
        } else {
          if( !is_folder && text.CmpNoCase( tree->GetItemText( child ) ) < 0 ) {
            result = last;
            break;
          }
        }
        last = child;
        child = tree->GetNextChild( parent, cookie );
      }
      if( !result.IsOk() ) {
        result = last;
      }
    }
    return result;
  }
  wxTreeItemId ProjectAddTreeNode( M_Code_Project* project, wxTreeCtrl* tree,  const wxString& text, const wxTreeItemId& parent,
                                   bool useFolders, FileTreeData::FileTreeDataKind folders_kind, bool compiles, int image,
                                   FileTreeData* data ) {
    wxTreeItemId ret;
    if( text.IsEmpty() ) {
      delete data;
      return ret;
    }
    wxString path = text;
    if( true && ( path.Length() > 1 ) && ( path.GetChar( 1 ) == _T( ':' ) ) ) {
      path.Remove( 1, 1 );
    }
    while( ( path.Length() > 1 ) && ( path.GetChar( 0 ) == _T( '\\' ) || path.GetChar( 0 ) == _T( '/' ) ) ) {
      path.Remove( 0, 1 );
    }
    if( path.IsEmpty() ) {
      delete data;
      return ret;
    }
    int pos = path.Find( _T( '/' ) );
    if( pos == -1 ) {
      pos = path.Find( _T( '\\' ) );
    }
    if( useFolders && pos >= 0 ) {
      wxString folder = path.Left( pos );
      while( path.GetChar( pos + 1 ) == _T( '/' ) || path.GetChar( pos + 1 ) == _T( '\\' ) ) {
        ++pos;
      }
      path = path.Right( path.Length() - pos - 1 );
      wxTreeItemIdValue cookie = nullptr;
      wxTreeItemId newparent = tree->GetFirstChild( parent, cookie );
      while( newparent ) {
        wxString itemText = tree->GetItemText( newparent );
        if( itemText.Matches( folder ) ) {
          break;
        }
        newparent = tree->GetNextChild( parent, cookie );
      }
      if( !newparent ) {
        int fldIdx  = cbProjectTreeImages::FolderIconIndex();
        int vfldIdx = cbProjectTreeImages::VirtualFolderIconIndex();
        newparent = ProjectFindNodeToInsertAfter( tree, folder, parent, true );
        FileTreeData* ftd = new FileTreeData( *data );
        ftd->SetKind( folders_kind );
        if( folders_kind != FileTreeData::ftdkVirtualFolder ) {
          ftd->SetFolder( project->GetCommonTopLevelPath() + GetRelativeFolderPath( tree, parent ) + folder + wxFILE_SEP_PATH );
        } else {
          ftd->SetFolder( GetRelativeFolderPath( tree, parent ) + folder + wxFILE_SEP_PATH );
        }
        ftd->SetProjectFile( nullptr );
        int idx = folders_kind != FileTreeData::ftdkVirtualFolder ? fldIdx : vfldIdx;
        newparent = tree->InsertItem( parent, newparent, folder, idx, idx, ftd );
      }
      ret = ProjectAddTreeNode( project, tree, path, newparent, true, folders_kind, compiles, image, data );
    } else {
      ret = tree->AppendItem( parent, text, image, image, data );
      if( !compiles ) {
        ColourManager *manager = Manager::Get()->GetColourManager();
        tree->SetItemTextColour( ret, manager->GetColour( _T( "project_tree_non_source_files" ) ) );
      }
    }
    return ret;
  }
  bool ProjectCanDragNode( M_Code_Project* project, wxTreeCtrl* tree, wxTreeItemId node ) {
    if( !node.IsOk() ) {
      return false;
    }
    FileTreeData* ftd = ( FileTreeData* )tree->GetItemData( node );
    if( !ftd ) {
      return false;
    }
    if( ftd->GetProject() != project ) {
      return false;
    }
    return ( ( ftd->GetKind() == FileTreeData::ftdkFile ) || ( ftd->GetKind() == FileTreeData::ftdkVirtualFolder ) );
  }
  void ProjectCopyTreeNodeRecursively( wxTreeCtrl* tree, const wxTreeItemId& item, const wxTreeItemId& new_parent ) {
    if( !tree || !item.IsOk() || !new_parent.IsOk() ) {
      return;
    }
    FileTreeData* ftd = ( FileTreeData* )tree->GetItemData( item );
    FileTreeData* ftd_moved = ftd ? new FileTreeData( *ftd ) : nullptr;
    int idx = tree->GetItemImage( item );
    wxColour col = tree->GetItemTextColour( item );
    wxTreeItemId insert = ProjectFindNodeToInsertAfter( tree, tree->GetItemText( item ), new_parent, ftd && ftd->GetKind() == FileTreeData::ftdkVirtualFolder );
    wxTreeItemId target = tree->InsertItem( new_parent, insert, tree->GetItemText( item ), idx, idx, ftd_moved );
    tree->SetItemTextColour( target, col );
    if( tree->ItemHasChildren( item ) ) {
      wxTreeItemIdValue cookie;
      wxTreeItemId child = tree->GetFirstChild( item, cookie );
      while( child.IsOk() ) {
        ProjectCopyTreeNodeRecursively( tree, child, target );
        child = tree->GetNextChild( item, cookie );
      }
    }
    if( !tree->IsExpanded( new_parent ) ) {
      tree->Expand( new_parent );
    }
    if( ftd_moved && ftd_moved->GetProjectFile() ) {
      ftd_moved->GetProjectFile()->virtual_path = GetRelativeFolderPath( tree, new_parent );
    }
  }
  bool ProjectVirtualFolderDragged( M_Code_Project* project, wxTreeCtrl* tree, wxTreeItemId from, wxTreeItemId to ) {
    FileTreeData* ftdFrom = static_cast<FileTreeData*>( tree->GetItemData( from ) );
    FileTreeData* ftdTo   = static_cast<FileTreeData*>( tree->GetItemData( to ) );
    if( !ftdFrom || !ftdTo ) {
      return false;
    }
    wxString sep = wxString( wxFileName::GetPathSeparator() );
    wxChar sepChar = wxFileName::GetPathSeparator();
    wxString fromFolderPath = ftdFrom->GetFolder();
    wxString toFolderPath = ftdTo->GetFolder();
    wxString fromFolder = fromFolderPath;
    fromFolder = fromFolder.RemoveLast();
    fromFolder = fromFolder.AfterLast( sepChar ) + sep;
    wxString toFolder = toFolderPath;
    toFolder = toFolder.RemoveLast();
    toFolder = toFolder.AfterLast( sepChar ) + sep;
    if( ftdFrom->GetKind() == FileTreeData::ftdkVirtualFolder && ftdTo->GetKind() == FileTreeData::ftdkVirtualFolder ) {
      const wxArrayString &oldArray = project->GetVirtualFolders();
      wxArrayString newFolders;
      for( size_t i = 0; i < oldArray.GetCount(); ++i ) {
        wxString item = oldArray[i];
        wxString toFolderStr;
        if( toFolderPath.StartsWith( fromFolderPath.BeforeFirst( sepChar ) ) ) {
          int posFrom = item.Find( fromFolderPath );
          if( posFrom != wxNOT_FOUND ) {
            wxString fromFolderStr = item.Mid( posFrom );
            item = item.Left( posFrom );
            if( !item.IsEmpty() )
            { newFolders.Add( item ); }
          } else if( item.IsSameAs( toFolderPath ) ) {
            newFolders.Add( item );
            wxString fromFolderStr = fromFolderPath;
            fromFolderStr = fromFolderStr.RemoveLast();
            fromFolderStr = fromFolderStr.AfterLast( wxFileName::GetPathSeparator() );
            newFolders.Add( item + fromFolderStr + sep );
          } else
          { newFolders.Add( item ); }
        } else {
          if( item.Find( toFolderPath + fromFolder ) != wxNOT_FOUND ) {
            msging( _T( "目标文件夹中存在另一个同名的虚拟文件夹!" ), _T( "错误" ), wxOK | wxICON_ERROR, Manager::Get()->GetAppWindow() );
            return false;
          }
          if( item.StartsWith( toFolderPath.BeforeFirst( sepChar ) ) ) {
            newFolders.Add( item );
          } else if( item.StartsWith( fromFolderPath.BeforeFirst( sepChar ) ) ) {
            int pos = item.Find( fromFolder );
            if( pos == 0 ) {
              if( !toFolderPath.IsEmpty() )
              { newFolders.Add( toFolderPath + item ); }
            } else {
              wxString temp = item.Left( pos );
              newFolders.Add( item.Left( pos ) );
              if( !toFolderPath.IsEmpty() )
              { newFolders.Add( toFolderPath + item.Mid( pos ) ); }
            }
          }
        }
      }
      project->SetVirtualFolders( newFolders );
    } else if( ftdFrom->GetKind() == FileTreeData::ftdkVirtualFolder && ftdTo->GetKind() == FileTreeData::ftdkProject ) {
      const wxArrayString &oldArray = project->GetVirtualFolders();
      wxArrayString newFolders;
      newFolders.Add( fromFolder );
      for( size_t i = 0; i < oldArray.GetCount(); ++i ) {
        wxString item = oldArray[i];
        if( item.StartsWith( fromFolder ) ) {
          msging( _T( "项目树中存在另一个同名的虚拟文件夹!" ), _T( "错误" ), wxOK | wxICON_ERROR, Manager::Get()->GetAppWindow() );
          return false;
        } else if( item.StartsWith( fromFolderPath ) ) {
          int pos = item.Find( fromFolderPath );
          if( pos != wxNOT_FOUND ) {
            item = item.Mid( pos + fromFolderPath.Length() );
            if( item.Length() > 1 ) {
              item = item.Prepend( fromFolder );
              if( newFolders.Index( item ) == wxNOT_FOUND )
              { newFolders.Add( item ); }
            } else
            { continue; }
          } else
          { newFolders.Add( item ); }
        } else
        { newFolders.Add( item ); }
      }
      project->SetVirtualFolders( newFolders );
    }
    return true;
  }
  bool ProjectNodeDragged( M_Code_Project* project, wxTreeCtrl* tree, wxArrayTreeItemIds& fromArray, wxTreeItemId to ) {
    if( !to.IsOk() ) {
      return false;
    }
    FileTreeData* ftdTo = ( FileTreeData* )tree->GetItemData( to );
    if( !ftdTo ) {
      return false;
    }
    if( ftdTo->GetProject() != project ) {
      return false;
    }
    if( ( ftdTo->GetKind() != FileTreeData::ftdkFile )
        && ( ftdTo->GetKind() != FileTreeData::ftdkVirtualFolder )
        && ( ftdTo->GetKind() != FileTreeData::ftdkProject ) ) {
      return false;
    }
    wxTreeItemId parentTo = ftdTo->GetKind() == FileTreeData::ftdkFile ? tree->GetItemParent( to ) : to;
    size_t count = fromArray.Count();
    for( size_t i = 0; i < count; i++ ) {
      wxTreeItemId from = fromArray[i];
      if( !from.IsOk() ) {
        return false;
      }
      FileTreeData* ftdFrom = ( FileTreeData* )tree->GetItemData( from );
      if( !ftdFrom ) {
        return false;
      }
      if( ftdFrom->GetProject() != project ) {
        return false;
      }
      if( ( ftdFrom->GetKind() != FileTreeData::ftdkFile )
          && ( ftdFrom->GetKind() != FileTreeData::ftdkVirtualFolder ) ) {
        return false;
      }
      wxTreeItemId parentFrom = ftdFrom->GetKind() == FileTreeData::ftdkFile ? tree->GetItemParent( from ) : from;
      if( parentFrom == parentTo ) {
        return false;
      }
      if( ( ftdFrom->GetKind() == FileTreeData::ftdkVirtualFolder )
          || ( ftdTo->GetKind()   == FileTreeData::ftdkVirtualFolder ) ) {
        wxTreeItemId root = tree->GetRootItem();
        wxTreeItemId toParent = tree->GetItemParent( to );
        while( toParent != root ) {
          if( toParent == from ) {
            return false;
          }
          toParent = tree->GetItemParent( toParent );
        }
        if( !ProjectVirtualFolderDragged( project, tree, from, to ) ) {
          return false;
        }
      }
    }
    for( size_t i = 0; i < count; i++ ) {
      wxTreeItemId from = fromArray[i];
      ProjectCopyTreeNodeRecursively( tree, from, parentTo );
      tree->Delete( from );
    }
    project->SetModified( true );
    Manager::Get()->GetProjectManager()->GetUI().RebuildTree();
    return true;
  }
  bool ProjectHasVirtualFolder( const wxString &folderName, const wxArrayString &virtualFolders ) {
    for( size_t i = 0; i < virtualFolders.GetCount(); ++i ) {
      if( virtualFolders[i].StartsWith( folderName ) ) {
        msging( _T( "已存在同名的虚拟文件夹." ), _T( "错误" ), wxICON_WARNING );
        return false;
      }
    }
    return true;
  }
  bool ProjectVirtualFolderAdded( M_Code_Project* project, wxTreeCtrl* tree,
                                  wxTreeItemId parent_node, const wxString& virtual_folder ) {
    wxString foldername = GetRelativeFolderPath( tree, parent_node );
    foldername << virtual_folder;
    foldername.Replace( _T( "\\" ), _T( "/" ), true );
    if( foldername.Last() != wxFILE_SEP_PATH ) {
      foldername << wxFILE_SEP_PATH;
    }
    const wxArrayString &virtualFolders = project->GetVirtualFolders();
    if( !ProjectHasVirtualFolder( foldername, virtualFolders ) ) {
      return false;
    }
    project->AppendUniqueVirtualFolder( foldername );
    FileTreeData* ftd = new FileTreeData( project, FileTreeData::ftdkVirtualFolder );
    ftd->SetProjectFile( nullptr );
    ftd->SetFolder( foldername );
    int vfldIdx = cbProjectTreeImages::VirtualFolderIconIndex();
    ProjectAddTreeNode( project, tree, foldername, project->GetProjectNode(), true, FileTreeData::ftdkVirtualFolder, true, vfldIdx, ftd );
    if( !tree->IsExpanded( parent_node ) ) {
      tree->Expand( parent_node );
    }
    project->SetModified( true );
    return true;
  }

  void ProjectVirtualFolderDeleted( M_Code_Project* project, wxTreeCtrl* tree, wxTreeItemId node ) {
    if( !node.IsOk() ) {
      return;
    }
    FileTreeData* ftd = ( FileTreeData* )tree->GetItemData( node );
    if( !ftd ) {
      return;
    }
    if( ftd->GetProject() != project ) {
      return;
    }
    wxString foldername = GetRelativeFolderPath( tree, node );
    wxString parent_foldername = GetRelativeFolderPath( tree, tree->GetItemParent( node ) );
    project->RemoveVirtualFolders( foldername );
    if( !parent_foldername.IsEmpty() ) {
      project->AppendUniqueVirtualFolder( parent_foldername );
    }
  }
  bool ProjectVirtualFolderRenamed( M_Code_Project* project, wxTreeCtrl* tree, wxTreeItemId node, const wxString& new_name ) {
    if( new_name.IsEmpty() ) {
      return false;
    }
    if( new_name.First( _T( ';' ) ) != wxNOT_FOUND ||
        new_name.First( _T( '/' ) ) != wxNOT_FOUND ||
        new_name.First( _T( '\\' ) ) != wxNOT_FOUND ) {
      msging( _T( "虚拟文件夹名称不能包含这些特殊字符: \";\", \"\\\" or \"/\"." ), _T( "错误" ), wxICON_WARNING );
      return false;
    }
    if( !node.IsOk() ) {
      return false;
    }
    if( tree->GetItemText( node ) == new_name ) {
      return false;
    }
    FileTreeData* ftd = ( FileTreeData* )tree->GetItemData( node );
    if( !ftd ) {
      return false;
    }
    if( ftd->GetProject() != project ) {
      return false;
    }
    wxString old_foldername = GetRelativeFolderPath( tree, node );
    wxString new_foldername = GetRelativeFolderPath( tree, tree->GetItemParent( node ) ) + new_name + wxFILE_SEP_PATH;
    const wxArrayString &virtualFolders = project->GetVirtualFolders();
    if( !ProjectHasVirtualFolder( new_foldername, virtualFolders ) ) {
      return false;
    }
    project->ReplaceVirtualFolder( old_foldername, new_foldername );
    return true;
  }
  bool ProjectShowOptions( M_Code_Project* project ) {
    if( !project ) {
      return false;
    }
    ProjectOptionsDlg dlg( Manager::Get()->GetAppWindow(), project );
    PlaceWindow( &dlg );
    if( dlg.ShowModal() == wxID_OK ) {
      FilesList &filesList = project->GetFilesList();
      for( FilesList::iterator it = filesList.begin(); it != filesList.end(); ++it ) {
        if( ProjectFile* pf = *it ) {
          pf->UpdateFileDetails();
        }
      }
      CodeBlocksEvent event( cbEVT_PROJECT_OPTIONS_CHANGED );
      event.SetProject( project );
      Manager::Get()->ProcessEvent( event );
      return true;
    }
    return false;
  }
}

void ProjectManagerUI::BuildProjectTree( M_Code_Project* project, M_Code_TreeCtrl* tree, const wxTreeItemId& root, int ptvs, FilesGroupsAndMasks* fgam ) {
  if( !tree ) {
    return;
  }
  int  fldIdx = cbProjectTreeImages::FolderIconIndex();
  int  vfldIdx = cbProjectTreeImages::VirtualFolderIconIndex();
  bool read_only = ( !wxFile::Access( project->GetFilename().c_str(), wxFile::write ) );
  int  prjIdx = cbProjectTreeImages::ProjectIconIndex( read_only );
  tree->SetCompareFunction( ptvs );
  FileTreeData* ftd = new FileTreeData( project, FileTreeData::ftdkProject );
  project->SetProjectNode( tree->AppendItem( root, project->GetTitle(), prjIdx, prjIdx, ftd ) );
  wxTreeItemId  others, generated;
  others = generated = project->GetProjectNode();
  wxTreeItemId* pGroupNodes = nullptr;
  bool do_categorise = ( ( ptvs & ptvsCategorize ) && fgam );
  if( do_categorise ) {
    pGroupNodes = new wxTreeItemId[fgam->GetGroupsCount()];
    for( unsigned int i = 0; i < fgam->GetGroupsCount(); ++i ) {
      ftd = new FileTreeData( project, FileTreeData::ftdkVirtualGroup );
      ftd->SetFolder( fgam->GetGroupName( i ) );
      pGroupNodes[i] = tree->AppendItem( project->GetProjectNode(), fgam->GetGroupName( i ), fldIdx, fldIdx, ftd );
    }
    ftd = new FileTreeData( project, FileTreeData::ftdkVirtualGroup );
    generated = tree->AppendItem( project->GetProjectNode(), _T( "自动生成" ), fldIdx, fldIdx, ftd );
    ftd = new FileTreeData( project, FileTreeData::ftdkVirtualGroup );
    others = tree->AppendItem( project->GetProjectNode(), _T( "其他" ), fldIdx, fldIdx, ftd );
  }
  const wxArrayString& virtualFolders = project->GetVirtualFolders();
  for( size_t i = 0; i < virtualFolders.GetCount(); ++i ) {
    ftd = new FileTreeData( project, FileTreeData::ftdkVirtualFolder );
    ftd->SetFolder( virtualFolders[i] );
    ProjectAddTreeNode( project, tree, virtualFolders[i], project->GetProjectNode(), true,
                        FileTreeData::ftdkVirtualFolder, true, vfldIdx, ftd );
  }
  int count = 0;
  FilesList& fileList = project->GetFilesList();
  for( FilesList::iterator it = fileList.begin(); it != fileList.end(); ++it ) {
    ProjectFile* pf = *it;
    if( !pf ) {
      Manager::Get()->GetLogManager()->LogError( _T( "项目的文件列表似乎已损坏?!" ) );
      continue;
    }
    ftd = new FileTreeData( project, FileTreeData::ftdkFile );
    ftd->SetFileIndex( count++ );
    ftd->SetProjectFile( pf );
    ftd->SetFolder( pf->file.GetFullPath() );
    wxString nodetext = pf->relativeToCommonTopLevelPath;
    FileTreeData::FileTreeDataKind folders_kind = FileTreeData::ftdkFolder;
    wxTreeItemId parentNode = project->GetProjectNode();
    if( !pf->virtual_path.IsEmpty() ) {
      nodetext = pf->virtual_path + wxFILE_SEP_PATH + pf->file.GetFullName();
      folders_kind = FileTreeData::ftdkVirtualFolder;
      wxString slash = pf->virtual_path.Last() == wxFILE_SEP_PATH ? _T( "" ) : wxString( wxFILE_SEP_PATH );
      ftd->SetFolder( pf->virtual_path );
      project->AppendUniqueVirtualFolder( pf->virtual_path + slash );
    } else if( do_categorise && pGroupNodes ) {
      bool found = false;
      if( pf->AutoGeneratedBy() ) {
        parentNode = generated;
        found = true;
      } else {
        const wxFileName fname( pf->relativeToCommonTopLevelPath );
        const wxString &fnameFullname = fname.GetFullName();
        for( unsigned int i = 0; i < fgam->GetGroupsCount(); ++i ) {
          if( fgam->MatchesMask( fnameFullname, i ) ) {
            parentNode = pGroupNodes[i];
            found = true;
            break;
          }
        }
      }
      if( !found ) {
        parentNode = others;
      }
    }
    if( !( ptvs & ptvsUseFolders )
        && ( ptvs & ptvsHideFolderName )
        && ( folders_kind != FileTreeData::ftdkVirtualFolder ) ) {
      nodetext = pf->file.GetFullName();
    }
    bool useFolders = ( ptvs & ptvsUseFolders ) || ( folders_kind == FileTreeData::ftdkVirtualFolder );
    pf->SetTreeItemId( ProjectAddTreeNode( project, tree, nodetext, parentNode, useFolders, folders_kind,
                                           pf->compile, ( int )pf->GetFileState(), ftd ) );
  }
  if( do_categorise && pGroupNodes ) {
    for( unsigned int i = 0; i < fgam->GetGroupsCount(); ++i ) {
      if( tree->GetChildrenCount( pGroupNodes[i], false ) == 0 ) {
        tree->Delete( pGroupNodes[i] );
      }
    }
    if( tree->GetChildrenCount( others, false ) == 0 ) {
      tree->Delete( others );
    }
    if( tree->GetChildrenCount( generated, false ) == 0 ) {
      tree->Delete( generated );
    }
  }
  delete[] pGroupNodes;
  ProjectTreeSortChildrenRecursive( tree, project->GetProjectNode() );
  tree->Expand( project->GetProjectNode() );
}
